summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/vyos/accel_ppp.py2
-rw-r--r--python/vyos/accel_ppp_util.py6
-rw-r--r--python/vyos/airbag.py2
-rw-r--r--python/vyos/base.py21
-rw-r--r--python/vyos/component_version.py2
-rw-r--r--python/vyos/compose_config.py2
-rw-r--r--python/vyos/config.py12
-rw-r--r--python/vyos/config_mgmt.py27
-rw-r--r--python/vyos/configdep.py2
-rw-r--r--python/vyos/configdict.py29
-rw-r--r--python/vyos/configdiff.py2
-rw-r--r--python/vyos/configquery.py2
-rw-r--r--python/vyos/configsession.py104
-rw-r--r--python/vyos/configsource.py112
-rw-r--r--python/vyos/configtree.py27
-rw-r--r--python/vyos/configverify.py21
-rw-r--r--python/vyos/debug.py2
-rw-r--r--python/vyos/defaults.py28
-rw-r--r--python/vyos/ethtool.py2
-rwxr-xr-xpython/vyos/firewall.py27
-rw-r--r--python/vyos/frrender.py22
-rw-r--r--python/vyos/ifconfig/__init__.py2
-rw-r--r--python/vyos/ifconfig/afi.py2
-rw-r--r--python/vyos/ifconfig/bond.py2
-rw-r--r--python/vyos/ifconfig/bridge.py12
-rw-r--r--python/vyos/ifconfig/control.py2
-rw-r--r--python/vyos/ifconfig/dummy.py2
-rw-r--r--python/vyos/ifconfig/ethernet.py2
-rw-r--r--python/vyos/ifconfig/geneve.py2
-rw-r--r--python/vyos/ifconfig/input.py2
-rw-r--r--python/vyos/ifconfig/interface.py38
-rw-r--r--python/vyos/ifconfig/l2tpv3.py2
-rw-r--r--python/vyos/ifconfig/loopback.py2
-rw-r--r--python/vyos/ifconfig/macsec.py2
-rw-r--r--python/vyos/ifconfig/macvlan.py2
-rw-r--r--python/vyos/ifconfig/operational.py2
-rw-r--r--python/vyos/ifconfig/pppoe.py2
-rw-r--r--python/vyos/ifconfig/section.py2
-rw-r--r--python/vyos/ifconfig/sstpc.py2
-rw-r--r--python/vyos/ifconfig/tunnel.py2
-rw-r--r--python/vyos/ifconfig/veth.py2
-rw-r--r--python/vyos/ifconfig/vrrp.py2
-rw-r--r--python/vyos/ifconfig/vti.py2
-rw-r--r--python/vyos/ifconfig/vtun.py2
-rw-r--r--python/vyos/ifconfig/vxlan.py2
-rw-r--r--python/vyos/ifconfig/wireguard.py164
-rw-r--r--python/vyos/ifconfig/wireless.py2
-rw-r--r--python/vyos/ifconfig/wwan.py2
-rw-r--r--python/vyos/iflag.py2
-rw-r--r--python/vyos/include/__init__.py2
-rw-r--r--python/vyos/include/uapi/__init__.py2
-rw-r--r--python/vyos/include/uapi/linux/__init__.py2
-rw-r--r--python/vyos/include/uapi/linux/fib_rules.py2
-rw-r--r--python/vyos/include/uapi/linux/icmpv6.py2
-rw-r--r--python/vyos/include/uapi/linux/if_arp.py2
-rw-r--r--python/vyos/include/uapi/linux/lwtunnel.py2
-rw-r--r--python/vyos/include/uapi/linux/neighbour.py2
-rw-r--r--python/vyos/include/uapi/linux/rtnetlink.py2
-rw-r--r--python/vyos/initialsetup.py2
-rw-r--r--python/vyos/ioctl.py2
-rw-r--r--python/vyos/ipsec.py2
-rw-r--r--python/vyos/kea.py2
-rw-r--r--python/vyos/limericks.py2
-rw-r--r--python/vyos/load_config.py2
-rw-r--r--python/vyos/logger.py2
-rw-r--r--python/vyos/migrate.py2
-rw-r--r--python/vyos/nat.py2
-rw-r--r--python/vyos/opmode.py2
-rw-r--r--python/vyos/pki.py2
-rw-r--r--python/vyos/priority.py2
-rw-r--r--python/vyos/progressbar.py2
-rwxr-xr-xpython/vyos/proto/generate_dataclass.py2
-rw-r--r--python/vyos/proto/vycall_pb2.py29
-rw-r--r--python/vyos/proto/vyconf_client.py2
-rw-r--r--python/vyos/proto/vyconf_pb2.py93
-rw-r--r--python/vyos/proto/vyconf_proto.py379
-rw-r--r--python/vyos/qos/__init__.py2
-rw-r--r--python/vyos/qos/base.py2
-rw-r--r--python/vyos/qos/cake.py11
-rw-r--r--python/vyos/qos/droptail.py2
-rw-r--r--python/vyos/qos/fairqueue.py2
-rw-r--r--python/vyos/qos/fqcodel.py2
-rw-r--r--python/vyos/qos/limiter.py2
-rw-r--r--python/vyos/qos/netem.py2
-rw-r--r--python/vyos/qos/priority.py2
-rw-r--r--python/vyos/qos/randomdetect.py2
-rw-r--r--python/vyos/qos/ratelimiter.py2
-rw-r--r--python/vyos/qos/roundrobin.py2
-rw-r--r--python/vyos/qos/trafficshaper.py2
-rw-r--r--python/vyos/raid.py2
-rw-r--r--python/vyos/remote.py46
-rw-r--r--python/vyos/snmpv3_hashgen.py2
-rw-r--r--python/vyos/system/__init__.py2
-rw-r--r--python/vyos/system/compat.py2
-rw-r--r--python/vyos/system/disk.py2
-rw-r--r--python/vyos/system/grub.py2
-rw-r--r--python/vyos/system/grub_util.py2
-rw-r--r--python/vyos/system/image.py2
-rw-r--r--python/vyos/system/raid.py2
-rwxr-xr-xpython/vyos/template.py95
-rw-r--r--python/vyos/tpm.py2
-rw-r--r--python/vyos/utils/__init__.py2
-rw-r--r--python/vyos/utils/assertion.py2
-rw-r--r--python/vyos/utils/auth.py2
-rw-r--r--python/vyos/utils/backend.py94
-rw-r--r--python/vyos/utils/boot.py2
-rw-r--r--python/vyos/utils/commit.py80
-rw-r--r--python/vyos/utils/config.py2
-rw-r--r--python/vyos/utils/configfs.py2
-rw-r--r--python/vyos/utils/convert.py2
-rw-r--r--python/vyos/utils/cpu.py8
-rw-r--r--python/vyos/utils/dict.py2
-rw-r--r--python/vyos/utils/disk.py2
-rw-r--r--python/vyos/utils/error.py2
-rw-r--r--python/vyos/utils/file.py23
-rw-r--r--python/vyos/utils/io.py2
-rw-r--r--python/vyos/utils/kernel.py2
-rw-r--r--python/vyos/utils/list.py2
-rw-r--r--python/vyos/utils/locking.py2
-rw-r--r--python/vyos/utils/misc.py2
-rw-r--r--python/vyos/utils/network.py93
-rw-r--r--python/vyos/utils/permission.py2
-rw-r--r--python/vyos/utils/process.py50
-rw-r--r--python/vyos/utils/serial.py2
-rw-r--r--python/vyos/utils/session.py25
-rw-r--r--python/vyos/utils/strip_config.py2
-rw-r--r--python/vyos/utils/system.py2
-rw-r--r--python/vyos/utils/vti_updown_db.py2
-rw-r--r--python/vyos/version.py2
-rw-r--r--python/vyos/vyconf_session.py185
-rw-r--r--python/vyos/wanloadbalance.py2
-rw-r--r--python/vyos/xml_ref/__init__.py30
-rw-r--r--python/vyos/xml_ref/definition.py2
-rwxr-xr-xpython/vyos/xml_ref/generate_cache.py2
-rwxr-xr-xpython/vyos/xml_ref/generate_op_cache.py193
-rw-r--r--python/vyos/xml_ref/op_definition.py248
-rwxr-xr-xpython/vyos/xml_ref/update_cache.py2
137 files changed, 2147 insertions, 395 deletions
diff --git a/python/vyos/accel_ppp.py b/python/vyos/accel_ppp.py
index bae695fc3..b1160dc76 100644
--- a/python/vyos/accel_ppp.py
+++ b/python/vyos/accel_ppp.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2022-2024 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# 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
diff --git a/python/vyos/accel_ppp_util.py b/python/vyos/accel_ppp_util.py
index ae75e6654..85e8a964c 100644
--- a/python/vyos/accel_ppp_util.py
+++ b/python/vyos/accel_ppp_util.py
@@ -1,4 +1,4 @@
-# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -221,10 +221,12 @@ def verify_accel_ppp_ip_pool(vpn_config):
for interface, interface_config in vpn_config['interface'].items():
if dict_search('client_subnet', interface_config):
break
+ if dict_search('external_dhcp.dhcp_relay', interface_config):
+ break
else:
raise ConfigError(
'Local auth and noauth mode requires local client-ip-pool \
- or client-ipv6-pool or client-subnet to be configured!')
+ or client-ipv6-pool or client-subnet or dhcp-relay to be configured!')
else:
raise ConfigError(
"Local auth mode requires local client-ip-pool \
diff --git a/python/vyos/airbag.py b/python/vyos/airbag.py
index 3c7a144b7..a869daae8 100644
--- a/python/vyos/airbag.py
+++ b/python/vyos/airbag.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2020 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/base.py b/python/vyos/base.py
index ca96d96ce..67f92564e 100644
--- a/python/vyos/base.py
+++ b/python/vyos/base.py
@@ -1,4 +1,4 @@
-# Copyright 2018-2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -15,8 +15,7 @@
from textwrap import fill
-
-class BaseWarning:
+class UserMessage:
def __init__(self, header, message, **kwargs):
self.message = message
self.kwargs = kwargs
@@ -33,7 +32,6 @@ class BaseWarning:
messages = self.message.split('\n')
isfirstmessage = True
initial_indent = self.textinitindent
- print('')
for mes in messages:
mes = fill(mes, initial_indent=initial_indent,
subsequent_indent=self.standardindent, **self.kwargs)
@@ -44,17 +42,24 @@ class BaseWarning:
print('', flush=True)
+class Message():
+ def __init__(self, message, **kwargs):
+ self.Message = UserMessage('', message, **kwargs)
+ self.Message.print()
+
class Warning():
def __init__(self, message, **kwargs):
- self.BaseWarn = BaseWarning('WARNING: ', message, **kwargs)
- self.BaseWarn.print()
+ print('')
+ self.UserMessage = UserMessage('WARNING: ', message, **kwargs)
+ self.UserMessage.print()
class DeprecationWarning():
def __init__(self, message, **kwargs):
# Reformat the message and trim it to 72 characters in length
- self.BaseWarn = BaseWarning('DEPRECATION WARNING: ', message, **kwargs)
- self.BaseWarn.print()
+ print('')
+ self.UserMessage = UserMessage('DEPRECATION WARNING: ', message, **kwargs)
+ self.UserMessage.print()
class ConfigError(Exception):
diff --git a/python/vyos/component_version.py b/python/vyos/component_version.py
index 81d986658..136bd36e8 100644
--- a/python/vyos/component_version.py
+++ b/python/vyos/component_version.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/compose_config.py b/python/vyos/compose_config.py
index 79a8718c5..1e7837858 100644
--- a/python/vyos/compose_config.py
+++ b/python/vyos/compose_config.py
@@ -1,4 +1,4 @@
-# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/config.py b/python/vyos/config.py
index 546eeceab..6f7c76ca7 100644
--- a/python/vyos/config.py
+++ b/python/vyos/config.py
@@ -1,4 +1,4 @@
-# Copyright 2017-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -73,8 +73,11 @@ from vyos.xml_ref import ext_dict_merge
from vyos.xml_ref import relative_defaults
from vyos.utils.dict import get_sub_dict
from vyos.utils.dict import mangle_dict_keys
+from vyos.utils.boot import boot_configuration_complete
+from vyos.utils.backend import vyconf_backend
from vyos.configsource import ConfigSource
from vyos.configsource import ConfigSourceSession
+from vyos.configsource import ConfigSourceVyconfSession
class ConfigDict(dict):
_from_defaults = {}
@@ -131,8 +134,13 @@ class Config(object):
subtrees.
"""
def __init__(self, session_env=None, config_source=None):
+ self.vyconf_session = None
if config_source is None:
- self._config_source = ConfigSourceSession(session_env)
+ if vyconf_backend() and boot_configuration_complete():
+ self._config_source = ConfigSourceVyconfSession(session_env)
+ self.vyconf_session = self._config_source._vyconf_session
+ else:
+ self._config_source = ConfigSourceSession(session_env)
else:
if not isinstance(config_source, ConfigSource):
raise TypeError("config_source not of type ConfigSource")
diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py
index dd8910afb..51c6f2241 100644
--- a/python/vyos/config_mgmt.py
+++ b/python/vyos/config_mgmt.py
@@ -1,4 +1,4 @@
-# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -25,10 +25,12 @@ from filecmp import cmp
from datetime import datetime
from textwrap import dedent
from pathlib import Path
-from tabulate import tabulate
from shutil import copy, chown
+from subprocess import Popen
+from subprocess import DEVNULL
from urllib.parse import urlsplit
from urllib.parse import urlunsplit
+from tabulate import tabulate
from vyos.config import Config
from vyos.configtree import ConfigTree
@@ -44,6 +46,7 @@ from vyos.utils.io import ask_yes_no
from vyos.utils.boot import boot_configuration_complete
from vyos.utils.process import is_systemd_service_active
from vyos.utils.process import rc_cmd
+from vyos.defaults import DEFAULT_COMMIT_CONFIRM_MINUTES
SAVE_CONFIG = '/usr/libexec/vyos/vyos-save-config.py'
config_json = '/run/vyatta/config/config.json'
@@ -56,7 +59,6 @@ commit_hooks = {
'commit_archive': '02vyos-commit-archive',
}
-DEFAULT_TIME_MINUTES = 10
timer_name = 'commit-confirm'
config_file = os.path.join(directories['config'], 'config.boot')
@@ -144,14 +146,16 @@ class ConfigMgmt:
['system', 'config-management'],
key_mangling=('-', '_'),
get_first_key=True,
- with_defaults=True,
+ with_recursive_defaults=True,
)
self.max_revisions = int(d.get('commit_revisions', 0))
self.num_revisions = 0
self.locations = d.get('commit_archive', {}).get('location', [])
self.source_address = d.get('commit_archive', {}).get('source_address', '')
- self.reboot_unconfirmed = bool(d.get('commit_confirm') == 'reboot')
+ self.reboot_unconfirmed = bool(
+ d.get('commit_confirm', {}).get('action') == 'reboot'
+ )
self.config_dict = d
if config.exists(['system', 'host-name']):
@@ -181,7 +185,7 @@ class ConfigMgmt:
# Console script functions
#
def commit_confirm(
- self, minutes: int = DEFAULT_TIME_MINUTES, no_prompt: bool = False
+ self, minutes: int = DEFAULT_COMMIT_CONFIRM_MINUTES, no_prompt: bool = False
) -> Tuple[str, int]:
"""Commit with reload/reboot to saved config in 'minutes' minutes if
'confirm' call is not issued.
@@ -229,7 +233,14 @@ Proceed ?"""
else:
cmd = f'sudo -b /usr/libexec/vyos/commit-confirm-notify.py {minutes}'
- os.system(cmd)
+ Popen(
+ cmd.split(),
+ stdout=DEVNULL,
+ stderr=DEVNULL,
+ stdin=DEVNULL,
+ close_fds=True,
+ preexec_fn=os.setsid,
+ )
if self.reboot_unconfirmed:
msg = f'Initialized commit-confirm; {minutes} minutes to confirm before reboot'
@@ -805,7 +816,7 @@ def run():
'-t',
dest='minutes',
type=int,
- default=DEFAULT_TIME_MINUTES,
+ default=DEFAULT_COMMIT_CONFIRM_MINUTES,
help="Minutes until reboot, unless 'confirm'",
)
commit_confirm.add_argument(
diff --git a/python/vyos/configdep.py b/python/vyos/configdep.py
index 747af8dbe..04de66493 100644
--- a/python/vyos/configdep.py
+++ b/python/vyos/configdep.py
@@ -1,4 +1,4 @@
-# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index ff0a15933..d91d88d88 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -661,6 +661,7 @@ def get_accel_dict(config, base, chap_secrets, with_pki=False):
Return a dictionary with the necessary interface config keys.
"""
from vyos.utils.cpu import get_core_count
+ from vyos.utils.cpu import get_half_cpus
from vyos.template import is_ipv4
dict = config.get_config_dict(base, key_mangling=('-', '_'),
@@ -670,7 +671,16 @@ def get_accel_dict(config, base, chap_secrets, with_pki=False):
with_pki=with_pki)
# set CPUs cores to process requests
- dict.update({'thread_count' : get_core_count()})
+ match dict.get('thread_count'):
+ case 'all':
+ dict['thread_count'] = get_core_count()
+ case 'half':
+ dict['thread_count'] = get_half_cpus()
+ case str(x) if x.isdigit():
+ dict['thread_count'] = int(x)
+ case _:
+ dict['thread_count'] = get_core_count()
+
# we need to store the path to the secrets file
dict.update({'chap_secrets_file' : chap_secrets})
@@ -693,3 +703,18 @@ def get_accel_dict(config, base, chap_secrets, with_pki=False):
dict['authentication']['radius']['server'][server]['acct_port'] = '0'
return dict
+
+def get_flowtable_interfaces(config):
+ """
+ Return all interfaces used in flowtables
+ """
+ ft_base = ['firewall', 'flowtable']
+
+ if not config.exists(ft_base):
+ return []
+
+ ifaces = []
+ for ft_name in config.list_nodes(ft_base):
+ ifaces += config.return_values(ft_base + [ft_name, 'interface'])
+
+ return ifaces
diff --git a/python/vyos/configdiff.py b/python/vyos/configdiff.py
index b6d4a5558..5e21a16e5 100644
--- a/python/vyos/configdiff.py
+++ b/python/vyos/configdiff.py
@@ -1,4 +1,4 @@
-# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/configquery.py b/python/vyos/configquery.py
index 4c4ead0a3..e8a3c0f99 100644
--- a/python/vyos/configquery.py
+++ b/python/vyos/configquery.py
@@ -1,4 +1,4 @@
-# Copyright 2021-2025 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py
index a3be29881..50f93f890 100644
--- a/python/vyos/configsession.py
+++ b/python/vyos/configsession.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2019-2024 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or modify it under the terms of
# the GNU Lesser General Public License as published by the Free Software Foundation;
@@ -22,19 +22,24 @@ from vyos.defaults import directories
from vyos.utils.process import is_systemd_service_running
from vyos.utils.dict import dict_to_paths
from vyos.utils.boot import boot_configuration_complete
+from vyos.utils.backend import vyconf_backend
from vyos.vyconf_session import VyconfSession
+from vyos.base import Warning as Warn
+from vyos.defaults import DEFAULT_COMMIT_CONFIRM_MINUTES
-vyconf_backend = False
CLI_SHELL_API = '/bin/cli-shell-api'
SET = '/opt/vyatta/sbin/my_set'
DELETE = '/opt/vyatta/sbin/my_delete'
COMMENT = '/opt/vyatta/sbin/my_comment'
COMMIT = '/opt/vyatta/sbin/my_commit'
+COMMIT_CONFIRM = ['/usr/bin/config-mgmt', 'commit_confirm', '-y']
+CONFIRM = ['/usr/bin/config-mgmt', 'confirm']
DISCARD = '/opt/vyatta/sbin/my_discard'
SHOW_CONFIG = ['/bin/cli-shell-api', 'showConfig']
LOAD_CONFIG = ['/bin/cli-shell-api', 'loadFile']
MIGRATE_LOAD_CONFIG = ['/usr/libexec/vyos/vyos-load-config.py']
+MERGE_CONFIG = ['/usr/libexec/vyos/vyos-merge-config.py']
SAVE_CONFIG = ['/usr/libexec/vyos/vyos-save-config.py']
INSTALL_IMAGE = [
'/usr/libexec/vyos/op_mode/image_installer.py',
@@ -67,6 +72,7 @@ GENERATE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'generate']
SHOW = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'show']
RESET = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'reset']
REBOOT = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'reboot']
+RENEW = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'renew']
POWEROFF = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'poweroff']
OP_CMD_ADD = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'add']
OP_CMD_DELETE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'delete']
@@ -120,6 +126,10 @@ def inject_vyos_env(env):
env['vyos_sbin_dir'] = '/usr/sbin'
env['vyos_validators_dir'] = '/usr/libexec/vyos/validators'
+ # with the retirement of the Cstore backend, this will remain as the
+ # sole indication of legacy CLI config mode, as checked by VyconfSession
+ env['_OFR_CONFIGURE'] = 'ok'
+
# if running the vyos-configd daemon, inject the vyshim env var
if is_systemd_service_running('vyos-configd.service'):
env['vyshim'] = '/usr/sbin/vyshim'
@@ -136,7 +146,7 @@ class ConfigSession(object):
The write API of VyOS.
"""
- def __init__(self, session_id, app=APP):
+ def __init__(self, session_id, app=APP, shared=False):
"""
Creates a new config session.
@@ -164,37 +174,52 @@ class ConfigSession(object):
for k, v in env_list:
session_env[k] = v
+ session_env['CONFIGSESSION_PID'] = str(session_id)
+
self.__session_env = session_env
self.__session_env['COMMIT_VIA'] = app
self.__run_command([CLI_SHELL_API, 'setupSession'])
- if vyconf_backend and boot_configuration_complete():
- self._vyconf_session = VyconfSession(on_error=ConfigSessionError)
+ if vyconf_backend() and boot_configuration_complete():
+ self._vyconf_session = VyconfSession(
+ pid=session_id, on_error=ConfigSessionError
+ )
else:
self._vyconf_session = None
+ self.shared = shared
+
def __del__(self):
- try:
- output = (
- subprocess.check_output(
- [CLI_SHELL_API, 'teardownSession'], env=self.__session_env
+ if self.shared:
+ return
+ if self._vyconf_session is None:
+ try:
+ output = (
+ subprocess.check_output(
+ [CLI_SHELL_API, 'teardownSession'], env=self.__session_env
+ )
+ .decode()
+ .strip()
)
- .decode()
- .strip()
- )
- if output:
+ if output:
+ print(
+ 'cli-shell-api teardownSession output for sesion {0}: {1}'.format(
+ self.__session_id, output
+ ),
+ file=sys.stderr,
+ )
+ except Exception as e:
print(
- 'cli-shell-api teardownSession output for sesion {0}: {1}'.format(
- self.__session_id, output
- ),
+ 'Could not tear down session {0}: {1}'.format(self.__session_id, e),
file=sys.stderr,
)
- except Exception as e:
- print(
- 'Could not tear down session {0}: {1}'.format(self.__session_id, e),
- file=sys.stderr,
- )
+ else:
+ if self._vyconf_session.session_changed():
+ Warn('Exiting with uncommitted changes')
+ self._vyconf_session.discard()
+ self._vyconf_session.exit_config_mode()
+ self._vyconf_session.teardown()
def __run_command(self, cmd_list):
p = subprocess.Popen(
@@ -283,6 +308,22 @@ class ConfigSession(object):
return out
+ def commit_confirm(self, minutes: int = DEFAULT_COMMIT_CONFIRM_MINUTES):
+ if self._vyconf_session is None:
+ out = self.__run_command(COMMIT_CONFIRM + [f'-t {minutes}'])
+ else:
+ out = 'unimplemented'
+
+ return out
+
+ def confirm(self):
+ if self._vyconf_session is None:
+ out = self.__run_command(CONFIRM)
+ else:
+ out = 'unimplemented'
+
+ return out
+
def discard(self):
if self._vyconf_session is None:
self.__run_command([DISCARD])
@@ -293,7 +334,7 @@ class ConfigSession(object):
if self._vyconf_session is None:
config_data = self.__run_command(SHOW_CONFIG + path)
else:
- config_data, _ = self._vyconf_session.show_config()
+ config_data, _ = self._vyconf_session.show_config(path)
if format == 'raw':
return config_data
@@ -302,7 +343,7 @@ class ConfigSession(object):
if self._vyconf_session is None:
out = self.__run_command(LOAD_CONFIG + [file_path])
else:
- out, _ = self._vyconf_session.load_config(file=file_path)
+ out, _ = self._vyconf_session.load_config(file_name=file_path)
return out
@@ -319,7 +360,18 @@ class ConfigSession(object):
if self._vyconf_session is None:
out = self.__run_command(MIGRATE_LOAD_CONFIG + [file_path])
else:
- out, _ = self._vyconf_session.load_config(file=file_path, migrate=True)
+ out, _ = self._vyconf_session.load_config(file_name=file_path, migrate=True)
+
+ return out
+
+ def merge_config(self, file_path, destructive=False):
+ if self._vyconf_session is None:
+ destr = ['--destructive'] if destructive else []
+ out = self.__run_command(MERGE_CONFIG + [file_path] + destr)
+ else:
+ out, _ = self._vyconf_session.merge_config(
+ file_name=file_path, destructive=destructive
+ )
return out
@@ -369,6 +421,10 @@ class ConfigSession(object):
out = self.__run_command(RESET + path)
return out
+ def renew(self, path):
+ out = self.__run_command(RENEW + path)
+ return out
+
def poweroff(self, path):
out = self.__run_command(POWEROFF + path)
return out
diff --git a/python/vyos/configsource.py b/python/vyos/configsource.py
index 65cef5333..949216722 100644
--- a/python/vyos/configsource.py
+++ b/python/vyos/configsource.py
@@ -1,5 +1,5 @@
-# Copyright 2020-2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -17,9 +17,16 @@
import os
import re
import subprocess
+from typing import Union
from vyos.configtree import ConfigTree
from vyos.utils.boot import boot_configuration_complete
+from vyos.vyconf_session import VyconfSession
+from vyos.vyconf_session import VyconfSessionError
+from vyos.defaults import directories
+from vyos.xml_ref import is_tag
+from vyos.xml_ref import is_leaf
+from vyos.xml_ref import is_multi
class VyOSError(Exception):
"""
@@ -310,6 +317,109 @@ class ConfigSourceSession(ConfigSource):
except VyOSError:
return False
+class ConfigSourceVyconfSession(ConfigSource):
+ def __init__(self, session_env=None):
+ super().__init__()
+
+ if session_env:
+ self.__session_env = session_env
+ else:
+ self.__session_env = None
+
+ if session_env and 'CONFIGSESSION_PID' in session_env:
+ self.pid = int(session_env['CONFIGSESSION_PID'])
+ else:
+ self.pid = os.getppid()
+
+ self._vyconf_session = VyconfSession(pid=self.pid)
+ try:
+ out = self._vyconf_session.get_config()
+ except VyconfSessionError as e:
+ raise ConfigSourceError(f'Init error in {type(self)}: {e}')
+
+ session_dir = directories['vyconf_session_dir']
+
+ self.running_cache_path = os.path.join(session_dir, f'running_cache_{out}')
+ self.session_cache_path = os.path.join(session_dir, f'session_cache_{out}')
+
+ self._running_config = ConfigTree(internal=self.running_cache_path)
+ self._session_config = ConfigTree(internal=self.session_cache_path)
+
+ if os.path.isfile(self.running_cache_path):
+ os.remove(self.running_cache_path)
+ if os.path.isfile(self.session_cache_path):
+ os.remove(self.session_cache_path)
+
+ # N.B. level not yet implemented pending integration with legacy CLI
+ # cf. T7374
+ self._level = []
+
+ def get_level(self):
+ return self._level
+
+ def set_level(self):
+ pass
+
+ def session_changed(self):
+ """
+ Returns:
+ True if the config session has uncommited changes, False otherwise.
+ """
+ try:
+ return self._vyconf_session.session_changed()
+ except VyconfSessionError:
+ # no actionable session info on error
+ return False
+
+ def in_session(self):
+ """
+ Returns:
+ True if called from a configuration session, False otherwise.
+ """
+ return self._vyconf_session.in_session()
+
+ def show_config(self, path: Union[str,list] = None, default: str = None,
+ effective: bool = False):
+ """
+ Args:
+ path (str|list): Configuration tree path, or empty
+ default (str): Default value to return
+
+ Returns:
+ str: working configuration
+ """
+
+ if path is None:
+ path = []
+ if isinstance(path, str):
+ path = path.split()
+
+ ct = self._running_config if effective else self._session_config
+ with_node = True if self.is_tag(path) else False
+ ct_at_path = ct.get_subtree(path, with_node=with_node) if path else ct
+
+ res = ct_at_path.to_string().strip()
+
+ return res if res else default
+
+ def is_tag(self, path):
+ try:
+ return is_tag(path)
+ except ValueError:
+ return False
+
+ def is_leaf(self, path):
+ try:
+ return is_leaf(path)
+ except ValueError:
+ return False
+
+ def is_multi(self, path):
+ try:
+ return is_multi(path)
+ except ValueError:
+ return False
+
class ConfigSourceString(ConfigSource):
def __init__(self, running_config_text=None, session_config_text=None):
super().__init__()
diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py
index ff40fbad0..ba3f1e368 100644
--- a/python/vyos/configtree.py
+++ b/python/vyos/configtree.py
@@ -1,5 +1,5 @@
# configtree -- a standalone VyOS config file manipulation library (Python bindings)
-# Copyright (C) 2018-2025 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or modify it under the terms of
# the GNU Lesser General Public License as published by the Free Software Foundation;
@@ -194,6 +194,7 @@ class ConfigTree(object):
raise ValueError('Failed to read internal rep: {0}'.format(msg))
else:
self.__config = config
+ self.__version = ''
elif config_string is not None:
config_section, version_section = extract_version(config_string)
config_section = escape_backslash(config_section)
@@ -232,7 +233,7 @@ class ConfigTree(object):
return self.__version
def write_cache(self, file_name):
- self.__write_internal(self._get_config(), file_name)
+ self.__write_internal(self._get_config(), file_name.encode())
def to_string(self, ordered_values=False, no_version=False):
config_string = self.__to_string(self.__config, ordered_values).decode()
@@ -498,6 +499,28 @@ def union(left, right, libpath=LIBPATH):
return tree
+def merge(left, right, destructive=False, 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')
+
+ __lib = cdll.LoadLibrary(libpath)
+ __tree_merge = __lib.tree_merge
+ __tree_merge.argtypes = [c_bool, c_void_p, c_void_p]
+ __tree_merge.restype = c_void_p
+ __get_error = __lib.get_error
+ __get_error.argtypes = []
+ __get_error.restype = c_char_p
+
+ res = __tree_merge(destructive, left._get_config(), right._get_config())
+ tree = ConfigTree(address=res)
+
+ return tree
+
+
def mask_inclusive(left, right, libpath=LIBPATH):
if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)):
raise TypeError('Arguments must be instances of ConfigTree')
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index d5f443f15..cc4419913 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -1,4 +1,4 @@
-# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -527,6 +527,25 @@ def verify_pki_dh_parameters(config: dict, dh_name: str, min_key_size: int=0):
if dh_bits < min_key_size:
raise ConfigError(f'Minimum DH key-size is {min_key_size} bits!')
+def verify_pki_openssh_key(config: dict, key_name: str):
+ """
+ Common helper function user by PKI consumers to perform recurring
+ validation functions on OpenSSH keys
+ """
+ if 'pki' not in config:
+ raise ConfigError('PKI is not configured!')
+
+ if 'openssh' not in config['pki']:
+ raise ConfigError('PKI does not contain any OpenSSH keys!')
+
+ if key_name not in config['pki']['openssh']:
+ raise ConfigError(f'OpenSSH key "{key_name}" not found in configuration!')
+
+ if 'public' in config['pki']['openssh'][key_name]:
+ if not {'key', 'type'} <= set(config['pki']['openssh'][key_name]['public']):
+ raise ConfigError('Both public key and type must be defined for '\
+ f'OpenSSH public key "{key_name}"!')
+
def verify_eapol(config: dict):
"""
Common helper function used by interface implementations to perform
diff --git a/python/vyos/debug.py b/python/vyos/debug.py
index 6ce42b173..5b6e8172e 100644
--- a/python/vyos/debug.py
+++ b/python/vyos/debug.py
@@ -1,4 +1,4 @@
-# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py
index 7efccded6..fbb5a0393 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -1,4 +1,4 @@
-# Copyright 2018-2025 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -15,10 +15,10 @@
import os
-base_dir = '/usr/libexec/vyos/'
+base_dir = '/usr/libexec/vyos'
directories = {
- 'base' : base_dir,
+ 'base' : f'{base_dir}',
'data' : '/usr/share/vyos/',
'conf_mode' : f'{base_dir}/conf_mode',
'op_mode' : f'{base_dir}/op_mode',
@@ -39,14 +39,24 @@ directories = {
'completion_dir' : f'{base_dir}/completion',
'ca_certificates' : '/usr/local/share/ca-certificates/vyos',
'ppp_nexthop_dir' : '/run/ppp_nexthop',
- 'proto_path' : '/usr/share/vyos/vyconf'
+ 'proto_path' : '/usr/share/vyos/vyconf',
+ 'vyconf_session_dir' : f'{base_dir}/vyconf/session'
}
systemd_services = {
+ 'haproxy' : 'haproxy.service',
'syslog' : 'syslog.service',
'snmpd' : 'snmpd.service',
}
+internal_ports = {
+ 'certbot_haproxy' : 65080, # Certbot running behing haproxy
+}
+
+config_files = {
+ 'sshd_user_ca' : '/run/sshd/trusted_user_ca',
+}
+
config_status = '/tmp/vyos-config-status'
api_config_state = '/run/http-api-state'
frr_debug_enable = '/tmp/vyos.frr.debug'
@@ -63,8 +73,8 @@ config_default = os.path.join(directories['data'], 'config.boot.default')
rt_symbolic_names = {
# Standard routing tables for Linux & reserved IDs for VyOS
- 'default': 253, # Confusingly, a final fallthru, not the default.
- 'main': 254, # The actual global table used by iproute2 unless told otherwise.
+ 'default': 253, # Confusingly, a final fallthru, not the default.
+ 'main': 254, # The actual global table used by iproute2 unless told otherwise.
'local': 255, # Special kernel loopback table.
}
@@ -72,3 +82,9 @@ rt_global_vrf = rt_symbolic_names['main']
rt_global_table = rt_symbolic_names['main']
vyconfd_conf = '/etc/vyos/vyconfd.conf'
+
+DEFAULT_COMMIT_CONFIRM_MINUTES = 10
+
+commit_hooks = {'pre': '/etc/commit/pre-hooks.d',
+ 'post': '/etc/commit/post-hooks.d'
+ }
diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py
index 4710a5d40..6c362163c 100644
--- a/python/vyos/ethtool.py
+++ b/python/vyos/ethtool.py
@@ -1,4 +1,4 @@
-# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index 9c320c82d..b136b6fca 100755
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2021-2024 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# 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
@@ -319,7 +319,10 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
if group_name[0] == '!':
operator = '!='
group_name = group_name[1:]
- output.append(f'{ip_name} {prefix}addr {operator} @R_{group_name}')
+ if ip_name == 'ip':
+ output.append(f'{ip_name} {prefix}addr {operator} @R_{group_name}')
+ elif ip_name == 'ip6':
+ output.append(f'{ip_name} {prefix}addr {operator} @R6_{group_name}')
if 'mac_group' in group:
group_name = group['mac_group']
operator = ''
@@ -358,7 +361,7 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
if iiface[0] == '!':
operator = '!='
iiface = iiface[1:]
- output.append(f'iifname {operator} {{{iiface}}}')
+ output.append(f'iifname {operator} {{"{iiface}"}}')
elif 'group' in rule_conf['inbound_interface']:
iiface = rule_conf['inbound_interface']['group']
if iiface[0] == '!':
@@ -373,7 +376,7 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
if oiface[0] == '!':
operator = '!='
oiface = oiface[1:]
- output.append(f'oifname {operator} {{{oiface}}}')
+ output.append(f'oifname {operator} {{"{oiface}"}}')
elif 'group' in rule_conf['outbound_interface']:
oiface = rule_conf['outbound_interface']['group']
if oiface[0] == '!':
@@ -471,14 +474,14 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
output.append('gre version 1')
if gre_key:
- # The offset of the key within the packet shifts depending on the C-flag.
- # nftables cannot handle complex enough expressions to match multiple
+ # The offset of the key within the packet shifts depending on the C-flag.
+ # nftables cannot handle complex enough expressions to match multiple
# offsets based on bitfields elsewhere.
- # We enforce a specific match for the checksum flag in validation, so the
- # gre_flags dict will always have a 'checksum' key when gre_key is populated.
- if not gre_flags['checksum']:
+ # We enforce a specific match for the checksum flag in validation, so the
+ # gre_flags dict will always have a 'checksum' key when gre_key is populated.
+ if not gre_flags['checksum']:
# No "unset" child node means C is set, we offset key lookup +32 bits
- output.append(f'@th,64,32 == {gre_key}')
+ output.append(f'@th,64,32 == {gre_key}')
else:
output.append(f'@th,32,32 == {gre_key}')
@@ -637,7 +640,7 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
return " ".join(output)
def parse_gre_flags(flags, force_keyed=False):
- flag_map = { # nft does not have symbolic names for these.
+ flag_map = { # nft does not have symbolic names for these.
'checksum': 1<<0,
'routing': 1<<1,
'key': 1<<2,
@@ -648,7 +651,7 @@ def parse_gre_flags(flags, force_keyed=False):
include = 0
exclude = 0
for fl_name, fl_state in flags.items():
- if not fl_state:
+ if not fl_state:
include |= flag_map[fl_name]
else: # 'unset' child tag
exclude |= flag_map[fl_name]
diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py
index 524167d8b..f4ed69205 100644
--- a/python/vyos/frrender.py
+++ b/python/vyos/frrender.py
@@ -1,4 +1,4 @@
-# Copyright 2024-2025 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -543,6 +543,21 @@ def get_frrender_dict(conf, argv=None) -> dict:
elif conf.exists_effective(ospfv3_vrf_path):
vrf['name'][vrf_name]['protocols'].update({'ospfv3' : {'deleted' : ''}})
+ # We need to check the CLI if the RPKI node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ rpki_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'rpki']
+ if 'rpki' in vrf_config.get('protocols', []):
+ rpki = conf.get_config_dict(rpki_vrf_path, key_mangling=('-', '_'), get_first_key=True,
+ with_pki=True, with_recursive_defaults=True)
+ rpki_ssh_key_base = '/run/frr/id_rpki'
+ for cache, cache_config in rpki.get('cache',{}).items():
+ if 'ssh' in cache_config:
+ cache_config['ssh']['public_key_file'] = f'{rpki_ssh_key_base}_{cache}.pub'
+ cache_config['ssh']['private_key_file'] = f'{rpki_ssh_key_base}_{cache}'
+ vrf['name'][vrf_name]['protocols'].update({'rpki' : rpki})
+ elif conf.exists_effective(rpki_vrf_path):
+ vrf['name'][vrf_name]['protocols'].update({'rpki' : {'deleted' : ''}})
+
# We need to check the CLI if the static node is present and thus load in all the default
# values present on the CLI - that's why we have if conf.exists()
static_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'static']
@@ -675,7 +690,7 @@ class FRRender:
output += render_to_string('frr/ripngd.frr.j2', config_dict['ripng'])
output += '\n'
if 'rpki' in config_dict and 'deleted' not in config_dict['rpki']:
- output += render_to_string('frr/rpki.frr.j2', config_dict['rpki'])
+ output += render_to_string('frr/rpki.frr.j2', {'rpki': config_dict['rpki']})
output += '\n'
if 'segment_routing' in config_dict and 'deleted' not in config_dict['segment_routing']:
output += render_to_string('frr/zebra.segment_routing.frr.j2', config_dict['segment_routing'])
@@ -697,6 +712,9 @@ class FRRender:
debug('FRR: START CONFIGURATION RENDERING')
# we can not reload an empty file, thus we always embed the marker
output = '!\n'
+ # Enable FRR logging
+ output += 'log syslog\n'
+ output += 'log facility local7\n'
# Enable SNMP agentx support
# SNMP AgentX support cannot be disabled once enabled
if 'snmp' in config_dict:
diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py
index 206b2bba1..7838fa9a2 100644
--- a/python/vyos/ifconfig/__init__.py
+++ b/python/vyos/ifconfig/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/afi.py b/python/vyos/ifconfig/afi.py
index fd263d220..a391cb8a0 100644
--- a/python/vyos/ifconfig/afi.py
+++ b/python/vyos/ifconfig/afi.py
@@ -1,4 +1,4 @@
-# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py
index a659b9bd2..8a97243c5 100644
--- a/python/vyos/ifconfig/bond.py
+++ b/python/vyos/ifconfig/bond.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py
index f81026965..ba06e3757 100644
--- a/python/vyos/ifconfig/bridge.py
+++ b/python/vyos/ifconfig/bridge.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -376,6 +376,16 @@ class BridgeIf(Interface):
if 'priority' in interface_config:
lower.set_path_priority(interface_config['priority'])
+ # set BPDU guard
+ tmp = dict_search('bpdu_guard', interface_config)
+ value = '1' if (tmp != None) else '0'
+ lower.set_bpdu_guard(value)
+
+ # set root guard
+ tmp = dict_search('root_guard', interface_config)
+ value = '1' if (tmp != None) else '0'
+ lower.set_root_guard(value)
+
if 'enable_vlan' in config:
add_vlan = []
native_vlan_id = None
diff --git a/python/vyos/ifconfig/control.py b/python/vyos/ifconfig/control.py
index a886c1b9e..e5672ed50 100644
--- a/python/vyos/ifconfig/control.py
+++ b/python/vyos/ifconfig/control.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/dummy.py b/python/vyos/ifconfig/dummy.py
index 29a1965a3..93066c965 100644
--- a/python/vyos/ifconfig/dummy.py
+++ b/python/vyos/ifconfig/dummy.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py
index 93727bdf6..864a9d0bc 100644
--- a/python/vyos/ifconfig/ethernet.py
+++ b/python/vyos/ifconfig/ethernet.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/geneve.py b/python/vyos/ifconfig/geneve.py
index f53ef4166..7c5b7c0fb 100644
--- a/python/vyos/ifconfig/geneve.py
+++ b/python/vyos/ifconfig/geneve.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/input.py b/python/vyos/ifconfig/input.py
index 201d3cacb..6cb1eb64c 100644
--- a/python/vyos/ifconfig/input.py
+++ b/python/vyos/ifconfig/input.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 003a273c0..787364483 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2025 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -217,6 +217,16 @@ class Interface(Control):
'location': '/sys/class/net/{ifname}/brport/priority',
'errormsg': '{ifname} is not a bridge port member'
},
+ 'bpdu_guard': {
+ 'validate': assert_boolean,
+ 'location': '/sys/class/net/{ifname}/brport/bpdu_guard',
+ 'errormsg': '{ifname} is not a bridge port member'
+ },
+ 'root_guard': {
+ 'validate': assert_boolean,
+ 'location': '/sys/class/net/{ifname}/brport/root_block',
+ 'errormsg': '{ifname} is not a bridge port member'
+ },
'proxy_arp': {
'validate': assert_boolean,
'location': '/proc/sys/net/ipv4/conf/{ifname}/proxy_arp',
@@ -413,11 +423,11 @@ class Interface(Control):
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}" }}'
+ nft_command = f'delete element inet vrf_zones ct_iface_map {{ \'"{self.ifname}"\' }}'
self._nft_check_and_run(nft_command)
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} }}'
+ 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_ifindex(self):
@@ -1106,6 +1116,28 @@ class Interface(Control):
"""
self.set_interface('path_priority', priority)
+ def set_bpdu_guard(self, state):
+ """
+ Set BPDU guard state for a bridge port. When enabled, the port will be
+ disabled if it receives a BPDU packet.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').set_bpdu_guard(1)
+ """
+ self.set_interface('bpdu_guard', state)
+
+ def set_root_guard(self, state):
+ """
+ Set root guard state for a bridge port. When enabled, the port will be
+ disabled if it receives a superior BPDU that would make it a root port.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').set_root_guard(1)
+ """
+ self.set_interface('root_guard', state)
+
def set_port_isolation(self, on_or_off):
"""
Controls whether a given port will be isolated, which means it will be
diff --git a/python/vyos/ifconfig/l2tpv3.py b/python/vyos/ifconfig/l2tpv3.py
index dfaa006aa..ea9294e99 100644
--- a/python/vyos/ifconfig/l2tpv3.py
+++ b/python/vyos/ifconfig/l2tpv3.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/loopback.py b/python/vyos/ifconfig/loopback.py
index 13e8a2c50..f4fc2c906 100644
--- a/python/vyos/ifconfig/loopback.py
+++ b/python/vyos/ifconfig/loopback.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/macsec.py b/python/vyos/ifconfig/macsec.py
index 3b4dc223f..4d76a1d46 100644
--- a/python/vyos/ifconfig/macsec.py
+++ b/python/vyos/ifconfig/macsec.py
@@ -1,4 +1,4 @@
-# Copyright 2020-2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/macvlan.py b/python/vyos/ifconfig/macvlan.py
index fe948b920..7a26f9ef5 100644
--- a/python/vyos/ifconfig/macvlan.py
+++ b/python/vyos/ifconfig/macvlan.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/operational.py b/python/vyos/ifconfig/operational.py
index dc2742123..e60518948 100644
--- a/python/vyos/ifconfig/operational.py
+++ b/python/vyos/ifconfig/operational.py
@@ -1,4 +1,4 @@
-# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/pppoe.py b/python/vyos/ifconfig/pppoe.py
index 85ca3877e..4ca66cf4d 100644
--- a/python/vyos/ifconfig/pppoe.py
+++ b/python/vyos/ifconfig/pppoe.py
@@ -1,4 +1,4 @@
-# Copyright 2020-2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/section.py b/python/vyos/ifconfig/section.py
index 50273cf67..4ea606495 100644
--- a/python/vyos/ifconfig/section.py
+++ b/python/vyos/ifconfig/section.py
@@ -1,4 +1,4 @@
-# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/sstpc.py b/python/vyos/ifconfig/sstpc.py
index d92ef23dc..e43a2f177 100644
--- a/python/vyos/ifconfig/sstpc.py
+++ b/python/vyos/ifconfig/sstpc.py
@@ -1,4 +1,4 @@
-# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py
index df904f7d5..f96364161 100644
--- a/python/vyos/ifconfig/tunnel.py
+++ b/python/vyos/ifconfig/tunnel.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/veth.py b/python/vyos/ifconfig/veth.py
index 2c8709d20..f4075fa02 100644
--- a/python/vyos/ifconfig/veth.py
+++ b/python/vyos/ifconfig/veth.py
@@ -1,4 +1,4 @@
-# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/vrrp.py b/python/vyos/ifconfig/vrrp.py
index 3ee22706c..4949fe571 100644
--- a/python/vyos/ifconfig/vrrp.py
+++ b/python/vyos/ifconfig/vrrp.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/vti.py b/python/vyos/ifconfig/vti.py
index 78f5895f8..030aa1ed7 100644
--- a/python/vyos/ifconfig/vti.py
+++ b/python/vyos/ifconfig/vti.py
@@ -1,4 +1,4 @@
-# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/vtun.py b/python/vyos/ifconfig/vtun.py
index ee790f275..e6963ce5d 100644
--- a/python/vyos/ifconfig/vtun.py
+++ b/python/vyos/ifconfig/vtun.py
@@ -1,4 +1,4 @@
-# Copyright 2020-2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py
index 58844885b..0f55acf10 100644
--- a/python/vyos/ifconfig/vxlan.py
+++ b/python/vyos/ifconfig/vxlan.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py
index f5217aecb..c4e70056c 100644
--- a/python/vyos/ifconfig/wireguard.py
+++ b/python/vyos/ifconfig/wireguard.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2025 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -22,12 +22,13 @@ from tempfile import NamedTemporaryFile
from hurry.filesize import size
from hurry.filesize import alternative
+from vyos.base import Warning
from vyos.configquery import ConfigTreeQuery
from vyos.ifconfig import Interface
from vyos.ifconfig import Operational
from vyos.template import is_ipv6
from vyos.template import is_ipv4
-
+from vyos.utils.network import get_wireguard_peers
class WireGuardOperational(Operational):
def _dump(self):
"""Dump wireguard data in a python friendly way."""
@@ -51,7 +52,7 @@ class WireGuardOperational(Operational):
'private_key': None if private_key == '(none)' else private_key,
'public_key': None if public_key == '(none)' else public_key,
'listen_port': int(listen_port),
- 'fw_mark': None if fw_mark == 'off' else int(fw_mark),
+ 'fw_mark': None if fw_mark == 'off' else int(fw_mark, 16),
'peers': {},
}
else:
@@ -251,92 +252,131 @@ class WireGuardIf(Interface):
"""Get a synthetic MAC address."""
return self.get_mac_synthetic()
+ def get_peer_public_keys(self, config, disabled=False):
+ """Get list of configured peer public keys"""
+ if 'peer' not in config:
+ return []
+
+ public_keys = []
+
+ for _, peer_config in config['peer'].items():
+ if disabled == ('disable' in peer_config):
+ public_keys.append(peer_config['public_key'])
+
+ return public_keys
+
def update(self, config):
"""General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
interface setup code and provide a single point of entry when workin
on any interface."""
- tmp_file = NamedTemporaryFile('w')
- tmp_file.write(config['private_key'])
- tmp_file.flush()
# Wireguard base command is identical for every peer
base_cmd = f'wg set {self.ifname}'
+
interface_cmd = base_cmd
if 'port' in config:
interface_cmd += ' listen-port {port}'
if 'fwmark' in config:
interface_cmd += ' fwmark {fwmark}'
- interface_cmd += f' private-key {tmp_file.name}'
- interface_cmd = interface_cmd.format(**config)
- # T6490: execute command to ensure interface configured
- self._cmd(interface_cmd)
+ with NamedTemporaryFile('w') as tmp_file:
+ tmp_file.write(config['private_key'])
+ tmp_file.flush()
- # If no PSK is given remove it by using /dev/null - passing keys via
- # the shell (usually bash) is considered insecure, thus we use a file
- no_psk_file = '/dev/null'
+ interface_cmd += f' private-key {tmp_file.name}'
+ interface_cmd = interface_cmd.format(**config)
+ # T6490: execute command to ensure interface configured
+ self._cmd(interface_cmd)
+
+ current_peer_public_keys = get_wireguard_peers(self.ifname)
+
+ if 'rebuild_required' in config:
+ # Remove all existing peers that no longer exist in config
+ current_public_keys = self.get_peer_public_keys(config)
+ cmd_remove_peers = [f' peer {public_key} remove'
+ for public_key in current_peer_public_keys
+ if public_key not in current_public_keys]
+ if cmd_remove_peers:
+ self._cmd(base_cmd + ''.join(cmd_remove_peers))
if 'peer' in config:
+ # Group removal of disabled peers in one command
+ current_disabled_peers = self.get_peer_public_keys(config, disabled=True)
+ cmd_disabled_peers = [f' peer {public_key} remove'
+ for public_key in current_disabled_peers]
+ if cmd_disabled_peers:
+ self._cmd(base_cmd + ''.join(cmd_disabled_peers))
+
+ peer_cmds = []
+ peer_domain_cmds = []
+ peer_psk_files = []
+
for peer, peer_config in config['peer'].items():
# T4702: No need to configure this peer when it was explicitly
# marked as disabled - also active sessions are terminated as
# the public key was already removed when entering this method!
if 'disable' in peer_config:
- # remove peer if disabled, no error report even if peer not exists
- cmd = base_cmd + ' peer {public_key} remove'
- self._cmd(cmd.format(**peer_config))
continue
- psk_file = no_psk_file
-
# start of with a fresh 'wg' command
- peer_cmd = base_cmd + ' peer {public_key}'
+ peer_cmd = ' peer {public_key}'
- try:
- cmd = peer_cmd
-
- if 'preshared_key' in peer_config:
- psk_file = '/tmp/tmp.wireguard.psk'
- with open(psk_file, 'w') as f:
- f.write(peer_config['preshared_key'])
- cmd += f' preshared-key {psk_file}'
-
- # Persistent keepalive is optional
- if 'persistent_keepalive' in peer_config:
- cmd += ' persistent-keepalive {persistent_keepalive}'
-
- # Multiple allowed-ip ranges can be defined - ensure we are always
- # dealing with a list
- if isinstance(peer_config['allowed_ips'], str):
- peer_config['allowed_ips'] = [peer_config['allowed_ips']]
- cmd += ' allowed-ips ' + ','.join(peer_config['allowed_ips'])
-
- self._cmd(cmd.format(**peer_config))
-
- cmd = peer_cmd
-
- # Ensure peer is created even if dns not working
- if {'address', 'port'} <= set(peer_config):
- if is_ipv6(peer_config['address']):
- cmd += ' endpoint [{address}]:{port}'
- elif is_ipv4(peer_config['address']):
- cmd += ' endpoint {address}:{port}'
- else:
- # don't set endpoint if address uses domain name
- continue
- elif {'host_name', 'port'} <= set(peer_config):
- cmd += ' endpoint {host_name}:{port}'
-
- self._cmd(cmd.format(**peer_config), env={
+ cmd = peer_cmd
+
+ if 'preshared_key' in peer_config:
+ with NamedTemporaryFile(mode='w', delete=False) as tmp_file:
+ tmp_file.write(peer_config['preshared_key'])
+ tmp_file.flush()
+ cmd += f' preshared-key {tmp_file.name}'
+ peer_psk_files.append(tmp_file.name)
+ else:
+ # If no PSK is given remove it by using /dev/null - passing keys via
+ # the shell (usually bash) is considered insecure, thus we use a file
+ cmd += f' preshared-key /dev/null'
+
+ # Persistent keepalive is optional
+ if 'persistent_keepalive' in peer_config:
+ cmd += ' persistent-keepalive {persistent_keepalive}'
+
+ # Multiple allowed-ip ranges can be defined - ensure we are always
+ # dealing with a list
+ if isinstance(peer_config['allowed_ips'], str):
+ peer_config['allowed_ips'] = [peer_config['allowed_ips']]
+ cmd += ' allowed-ips ' + ','.join(peer_config['allowed_ips'])
+
+ peer_cmds.append(cmd.format(**peer_config))
+
+ cmd = peer_cmd
+
+ # Ensure peer is created even if dns not working
+ if {'address', 'port'} <= set(peer_config):
+ if is_ipv6(peer_config['address']):
+ cmd += ' endpoint [{address}]:{port}'
+ elif is_ipv4(peer_config['address']):
+ cmd += ' endpoint {address}:{port}'
+ else:
+ # don't set endpoint if address uses domain name
+ continue
+ elif {'host_name', 'port'} <= set(peer_config):
+ cmd += ' endpoint {host_name}:{port}'
+ else:
+ continue
+
+ peer_domain_cmds.append(cmd.format(**peer_config))
+
+ try:
+ if peer_cmds:
+ self._cmd(base_cmd + ''.join(peer_cmds))
+
+ if peer_domain_cmds:
+ self._cmd(base_cmd + ''.join(peer_domain_cmds), env={
'WG_ENDPOINT_RESOLUTION_RETRIES': config['max_dns_retry']})
- except:
- # todo: logging
- pass
- finally:
- # PSK key file is not required to be stored persistently as its backed by CLI
- if psk_file != no_psk_file and os.path.exists(psk_file):
- os.remove(psk_file)
+ except Exception as e:
+ Warning(f'Failed to apply Wireguard peers on {self.ifname}: {e}')
+ finally:
+ for tmp in peer_psk_files:
+ os.unlink(tmp)
# call base class
super().update(config)
diff --git a/python/vyos/ifconfig/wireless.py b/python/vyos/ifconfig/wireless.py
index 121f56bd5..69fd87347 100644
--- a/python/vyos/ifconfig/wireless.py
+++ b/python/vyos/ifconfig/wireless.py
@@ -1,4 +1,4 @@
-# Copyright 2020-2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ifconfig/wwan.py b/python/vyos/ifconfig/wwan.py
index 004a64b39..2b5714b85 100644
--- a/python/vyos/ifconfig/wwan.py
+++ b/python/vyos/ifconfig/wwan.py
@@ -1,4 +1,4 @@
-# Copyright 2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/iflag.py b/python/vyos/iflag.py
index 3ce73c1bf..179f33497 100644
--- a/python/vyos/iflag.py
+++ b/python/vyos/iflag.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/include/__init__.py b/python/vyos/include/__init__.py
index 22e836531..ba196ffed 100644
--- a/python/vyos/include/__init__.py
+++ b/python/vyos/include/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2025 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/include/uapi/__init__.py b/python/vyos/include/uapi/__init__.py
index 22e836531..ba196ffed 100644
--- a/python/vyos/include/uapi/__init__.py
+++ b/python/vyos/include/uapi/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2025 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/include/uapi/linux/__init__.py b/python/vyos/include/uapi/linux/__init__.py
index 22e836531..ba196ffed 100644
--- a/python/vyos/include/uapi/linux/__init__.py
+++ b/python/vyos/include/uapi/linux/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2025 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/include/uapi/linux/fib_rules.py b/python/vyos/include/uapi/linux/fib_rules.py
index 72f0b18cb..83544f69b 100644
--- a/python/vyos/include/uapi/linux/fib_rules.py
+++ b/python/vyos/include/uapi/linux/fib_rules.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2025 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# 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
diff --git a/python/vyos/include/uapi/linux/icmpv6.py b/python/vyos/include/uapi/linux/icmpv6.py
index 47e0c723c..cc30b76fd 100644
--- a/python/vyos/include/uapi/linux/icmpv6.py
+++ b/python/vyos/include/uapi/linux/icmpv6.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2025 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# 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
diff --git a/python/vyos/include/uapi/linux/if_arp.py b/python/vyos/include/uapi/linux/if_arp.py
index 90cb66ebd..80c16a83d 100644
--- a/python/vyos/include/uapi/linux/if_arp.py
+++ b/python/vyos/include/uapi/linux/if_arp.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2025 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# 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
diff --git a/python/vyos/include/uapi/linux/lwtunnel.py b/python/vyos/include/uapi/linux/lwtunnel.py
index 6797a762b..c598513a5 100644
--- a/python/vyos/include/uapi/linux/lwtunnel.py
+++ b/python/vyos/include/uapi/linux/lwtunnel.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2025 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# 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
diff --git a/python/vyos/include/uapi/linux/neighbour.py b/python/vyos/include/uapi/linux/neighbour.py
index d5caf44b9..8878353e3 100644
--- a/python/vyos/include/uapi/linux/neighbour.py
+++ b/python/vyos/include/uapi/linux/neighbour.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2025 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# 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
diff --git a/python/vyos/include/uapi/linux/rtnetlink.py b/python/vyos/include/uapi/linux/rtnetlink.py
index e31272460..f3778fa65 100644
--- a/python/vyos/include/uapi/linux/rtnetlink.py
+++ b/python/vyos/include/uapi/linux/rtnetlink.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2025 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# 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
diff --git a/python/vyos/initialsetup.py b/python/vyos/initialsetup.py
index cb6b9e459..bff3adf20 100644
--- a/python/vyos/initialsetup.py
+++ b/python/vyos/initialsetup.py
@@ -1,7 +1,7 @@
# initialsetup -- functions for setting common values in config file,
# for use in installation and first boot scripts
#
-# Copyright (C) 2018-2024 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or modify it under the terms of
# the GNU Lesser General Public License as published by the Free Software Foundation;
diff --git a/python/vyos/ioctl.py b/python/vyos/ioctl.py
index 51574c1db..7f9ad226a 100644
--- a/python/vyos/ioctl.py
+++ b/python/vyos/ioctl.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/ipsec.py b/python/vyos/ipsec.py
index 28f77565a..81f3d0812 100644
--- a/python/vyos/ipsec.py
+++ b/python/vyos/ipsec.py
@@ -1,4 +1,4 @@
-# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/kea.py b/python/vyos/kea.py
index 5eecbbaad..15c8564b0 100644
--- a/python/vyos/kea.py
+++ b/python/vyos/kea.py
@@ -1,4 +1,4 @@
-# Copyright 2023-2025 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/limericks.py b/python/vyos/limericks.py
index 3c6744816..0c02d5292 100644
--- a/python/vyos/limericks.py
+++ b/python/vyos/limericks.py
@@ -1,4 +1,4 @@
-# Copyright 2015, 2018 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/load_config.py b/python/vyos/load_config.py
index b910a2f92..f65e887f0 100644
--- a/python/vyos/load_config.py
+++ b/python/vyos/load_config.py
@@ -1,4 +1,4 @@
-# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/logger.py b/python/vyos/logger.py
index f7cc964d5..207f95c1b 100644
--- a/python/vyos/logger.py
+++ b/python/vyos/logger.py
@@ -1,4 +1,4 @@
-# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/migrate.py b/python/vyos/migrate.py
index 9d1613676..c06f6a76c 100644
--- a/python/vyos/migrate.py
+++ b/python/vyos/migrate.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/nat.py b/python/vyos/nat.py
index 29f8e961b..7be957a0c 100644
--- a/python/vyos/nat.py
+++ b/python/vyos/nat.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# 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
diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py
index 7b11d36dd..7f1fc6b4f 100644
--- a/python/vyos/opmode.py
+++ b/python/vyos/opmode.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/pki.py b/python/vyos/pki.py
index 55dc02631..4598c5daa 100644
--- a/python/vyos/pki.py
+++ b/python/vyos/pki.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2023-2024 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# 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
diff --git a/python/vyos/priority.py b/python/vyos/priority.py
index ab4e6d411..e61281d3c 100644
--- a/python/vyos/priority.py
+++ b/python/vyos/priority.py
@@ -1,4 +1,4 @@
-# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/progressbar.py b/python/vyos/progressbar.py
index 8d1042672..eb8ed474a 100644
--- a/python/vyos/progressbar.py
+++ b/python/vyos/progressbar.py
@@ -1,4 +1,4 @@
-# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/proto/generate_dataclass.py b/python/vyos/proto/generate_dataclass.py
index c6296c568..64485cd10 100755
--- a/python/vyos/proto/generate_dataclass.py
+++ b/python/vyos/proto/generate_dataclass.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2025 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# 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
diff --git a/python/vyos/proto/vycall_pb2.py b/python/vyos/proto/vycall_pb2.py
new file mode 100644
index 000000000..95214d2a6
--- /dev/null
+++ b/python/vyos/proto/vycall_pb2.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: vycall.proto
+"""Generated protocol buffer code."""
+from google.protobuf.internal import builder as _builder
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cvycall.proto\"&\n\x06Status\x12\x0f\n\x07success\x18\x01 \x02(\x08\x12\x0b\n\x03out\x18\x02 \x02(\t\"Y\n\x04\x43\x61ll\x12\x13\n\x0bscript_name\x18\x01 \x02(\t\x12\x11\n\ttag_value\x18\x02 \x01(\t\x12\x11\n\targ_value\x18\x03 \x01(\t\x12\x16\n\x05reply\x18\x04 \x01(\x0b\x32\x07.Status\"~\n\x06\x43ommit\x12\x12\n\nsession_id\x18\x01 \x02(\t\x12\x0f\n\x07\x64ry_run\x18\x04 \x02(\x08\x12\x0e\n\x06\x61tomic\x18\x05 \x02(\x08\x12\x12\n\nbackground\x18\x06 \x02(\x08\x12\x15\n\x04init\x18\x07 \x01(\x0b\x32\x07.Status\x12\x14\n\x05\x63\x61lls\x18\x08 \x03(\x0b\x32\x05.Call')
+
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'vycall_pb2', globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
+
+ DESCRIPTOR._options = None
+ _STATUS._serialized_start=16
+ _STATUS._serialized_end=54
+ _CALL._serialized_start=56
+ _CALL._serialized_end=145
+ _COMMIT._serialized_start=147
+ _COMMIT._serialized_end=273
+# @@protoc_insertion_point(module_scope)
diff --git a/python/vyos/proto/vyconf_client.py b/python/vyos/proto/vyconf_client.py
index b385f0951..a3ba9864c 100644
--- a/python/vyos/proto/vyconf_client.py
+++ b/python/vyos/proto/vyconf_client.py
@@ -1,4 +1,4 @@
-# Copyright 2025 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/proto/vyconf_pb2.py b/python/vyos/proto/vyconf_pb2.py
new file mode 100644
index 000000000..4bf0eb2e0
--- /dev/null
+++ b/python/vyos/proto/vyconf_pb2.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: vyconf.proto
+"""Generated protocol buffer code."""
+from google.protobuf.internal import builder as _builder
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cvyconf.proto\"\xae\x15\n\x07Request\x12!\n\x06prompt\x18\x01 \x01(\x0b\x32\x0f.Request.PromptH\x00\x12.\n\rsetup_session\x18\x02 \x01(\x0b\x32\x15.Request.SetupSessionH\x00\x12\x1b\n\x03set\x18\x03 \x01(\x0b\x32\x0c.Request.SetH\x00\x12!\n\x06\x64\x65lete\x18\x04 \x01(\x0b\x32\x0f.Request.DeleteH\x00\x12!\n\x06rename\x18\x05 \x01(\x0b\x32\x0f.Request.RenameH\x00\x12\x1d\n\x04\x63opy\x18\x06 \x01(\x0b\x32\r.Request.CopyH\x00\x12#\n\x07\x63omment\x18\x07 \x01(\x0b\x32\x10.Request.CommentH\x00\x12!\n\x06\x63ommit\x18\x08 \x01(\x0b\x32\x0f.Request.CommitH\x00\x12%\n\x08rollback\x18\t \x01(\x0b\x32\x11.Request.RollbackH\x00\x12\x1f\n\x05merge\x18\n \x01(\x0b\x32\x0e.Request.MergeH\x00\x12\x1d\n\x04save\x18\x0b \x01(\x0b\x32\r.Request.SaveH\x00\x12*\n\x0bshow_config\x18\x0c \x01(\x0b\x32\x13.Request.ShowConfigH\x00\x12!\n\x06\x65xists\x18\r \x01(\x0b\x32\x0f.Request.ExistsH\x00\x12&\n\tget_value\x18\x0e \x01(\x0b\x32\x11.Request.GetValueH\x00\x12(\n\nget_values\x18\x0f \x01(\x0b\x32\x12.Request.GetValuesH\x00\x12.\n\rlist_children\x18\x10 \x01(\x0b\x32\x15.Request.ListChildrenH\x00\x12)\n\x0brun_op_mode\x18\x11 \x01(\x0b\x32\x12.Request.RunOpModeH\x00\x12#\n\x07\x63onfirm\x18\x12 \x01(\x0b\x32\x10.Request.ConfirmH\x00\x12\x43\n\x18\x65nter_configuration_mode\x18\x13 \x01(\x0b\x32\x1f.Request.EnterConfigurationModeH\x00\x12\x41\n\x17\x65xit_configuration_mode\x18\x14 \x01(\x0b\x32\x1e.Request.ExitConfigurationModeH\x00\x12%\n\x08validate\x18\x15 \x01(\x0b\x32\x11.Request.ValidateH\x00\x12%\n\x08teardown\x18\x16 \x01(\x0b\x32\x11.Request.TeardownH\x00\x12\x30\n\x0ereload_reftree\x18\x17 \x01(\x0b\x32\x16.Request.ReloadReftreeH\x00\x12\x1d\n\x04load\x18\x18 \x01(\x0b\x32\r.Request.LoadH\x00\x12#\n\x07\x64iscard\x18\x19 \x01(\x0b\x32\x10.Request.DiscardH\x00\x12\x32\n\x0fsession_changed\x18\x1a \x01(\x0b\x32\x17.Request.SessionChangedH\x00\x12/\n\x0esession_of_pid\x18\x1b \x01(\x0b\x32\x15.Request.SessionOfPidH\x00\x12\x37\n\x12session_update_pid\x18\x1c \x01(\x0b\x32\x19.Request.SessionUpdatePidH\x00\x12(\n\nget_config\x18\x1d \x01(\x0b\x32\x12.Request.GetConfigH\x00\x1a\x08\n\x06Prompt\x1aP\n\x0cSetupSession\x12\x11\n\tClientPid\x18\x01 \x02(\x05\x12\x19\n\x11\x43lientApplication\x18\x02 \x01(\t\x12\x12\n\nOnBehalfOf\x18\x03 \x01(\x05\x1a!\n\x0cSessionOfPid\x12\x11\n\tClientPid\x18\x01 \x02(\x05\x1a%\n\x10SessionUpdatePid\x12\x11\n\tClientPid\x18\x01 \x02(\x05\x1a\x1a\n\tGetConfig\x12\r\n\x05\x64ummy\x18\x01 \x01(\x05\x1a\x1e\n\x08Teardown\x12\x12\n\nOnBehalfOf\x18\x01 \x01(\x05\x1a\x46\n\x08Validate\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12,\n\routput_format\x18\x02 \x01(\x0e\x32\x15.Request.OutputFormat\x1a\x13\n\x03Set\x12\x0c\n\x04Path\x18\x01 \x03(\t\x1a\x16\n\x06\x44\x65lete\x12\x0c\n\x04Path\x18\x01 \x03(\t\x1a\x18\n\x07\x44iscard\x12\r\n\x05\x64ummy\x18\x01 \x01(\x05\x1a\x1f\n\x0eSessionChanged\x12\r\n\x05\x64ummy\x18\x01 \x01(\x05\x1a\x35\n\x06Rename\x12\x11\n\tEditLevel\x18\x01 \x03(\t\x12\x0c\n\x04\x46rom\x18\x02 \x02(\t\x12\n\n\x02To\x18\x03 \x02(\t\x1a\x33\n\x04\x43opy\x12\x11\n\tEditLevel\x18\x01 \x03(\t\x12\x0c\n\x04\x46rom\x18\x02 \x02(\t\x12\n\n\x02To\x18\x03 \x02(\t\x1a(\n\x07\x43omment\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12\x0f\n\x07\x43omment\x18\x02 \x02(\t\x1aR\n\x06\x43ommit\x12\x0f\n\x07\x43onfirm\x18\x01 \x01(\x08\x12\x16\n\x0e\x43onfirmTimeout\x18\x02 \x01(\x05\x12\x0f\n\x07\x43omment\x18\x03 \x01(\t\x12\x0e\n\x06\x44ryRun\x18\x04 \x01(\x08\x1a\x1c\n\x08Rollback\x12\x10\n\x08Revision\x18\x01 \x02(\x05\x1aO\n\x04Load\x12\x10\n\x08Location\x18\x01 \x02(\t\x12\x0e\n\x06\x63\x61\x63hed\x18\x02 \x02(\x08\x12%\n\x06\x66ormat\x18\x03 \x01(\x0e\x32\x15.Request.ConfigFormat\x1aU\n\x05Merge\x12\x10\n\x08Location\x18\x01 \x02(\t\x12\x13\n\x0b\x64\x65structive\x18\x02 \x02(\x08\x12%\n\x06\x66ormat\x18\x03 \x01(\x0e\x32\x15.Request.ConfigFormat\x1a?\n\x04Save\x12\x10\n\x08Location\x18\x01 \x02(\t\x12%\n\x06\x66ormat\x18\x02 \x01(\x0e\x32\x15.Request.ConfigFormat\x1a\x41\n\nShowConfig\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12%\n\x06\x66ormat\x18\x02 \x01(\x0e\x32\x15.Request.ConfigFormat\x1a\x16\n\x06\x45xists\x12\x0c\n\x04Path\x18\x01 \x03(\t\x1a\x46\n\x08GetValue\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12,\n\routput_format\x18\x02 \x01(\x0e\x32\x15.Request.OutputFormat\x1aG\n\tGetValues\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12,\n\routput_format\x18\x02 \x01(\x0e\x32\x15.Request.OutputFormat\x1aJ\n\x0cListChildren\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12,\n\routput_format\x18\x02 \x01(\x0e\x32\x15.Request.OutputFormat\x1aG\n\tRunOpMode\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12,\n\routput_format\x18\x02 \x01(\x0e\x32\x15.Request.OutputFormat\x1a\t\n\x07\x43onfirm\x1a\x46\n\x16\x45nterConfigurationMode\x12\x11\n\tExclusive\x18\x01 \x02(\x08\x12\x19\n\x11OverrideExclusive\x18\x02 \x02(\x08\x1a\x17\n\x15\x45xitConfigurationMode\x1a#\n\rReloadReftree\x12\x12\n\nOnBehalfOf\x18\x01 \x01(\x05\"#\n\x0c\x43onfigFormat\x12\t\n\x05\x43URLY\x10\x00\x12\x08\n\x04JSON\x10\x01\")\n\x0cOutputFormat\x12\x0c\n\x08OutPlain\x10\x00\x12\x0b\n\x07OutJSON\x10\x01\x42\x05\n\x03msg\";\n\x0fRequestEnvelope\x12\r\n\x05token\x18\x01 \x01(\t\x12\x19\n\x07request\x18\x02 \x02(\x0b\x32\x08.Request\"S\n\x08Response\x12\x17\n\x06status\x18\x01 \x02(\x0e\x32\x07.Errnum\x12\x0e\n\x06output\x18\x02 \x01(\t\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12\x0f\n\x07warning\x18\x04 \x01(\t*\xd2\x01\n\x06\x45rrnum\x12\x0b\n\x07SUCCESS\x10\x00\x12\x08\n\x04\x46\x41IL\x10\x01\x12\x10\n\x0cINVALID_PATH\x10\x02\x12\x11\n\rINVALID_VALUE\x10\x03\x12\x16\n\x12\x43OMMIT_IN_PROGRESS\x10\x04\x12\x18\n\x14\x43ONFIGURATION_LOCKED\x10\x05\x12\x12\n\x0eINTERNAL_ERROR\x10\x06\x12\x15\n\x11PERMISSION_DENIED\x10\x07\x12\x17\n\x13PATH_ALREADY_EXISTS\x10\x08\x12\x16\n\x12UNCOMMITED_CHANGES\x10\t')
+
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'vyconf_pb2', globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
+
+ DESCRIPTOR._options = None
+ _ERRNUM._serialized_start=2900
+ _ERRNUM._serialized_end=3110
+ _REQUEST._serialized_start=17
+ _REQUEST._serialized_end=2751
+ _REQUEST_PROMPT._serialized_start=1237
+ _REQUEST_PROMPT._serialized_end=1245
+ _REQUEST_SETUPSESSION._serialized_start=1247
+ _REQUEST_SETUPSESSION._serialized_end=1327
+ _REQUEST_SESSIONOFPID._serialized_start=1329
+ _REQUEST_SESSIONOFPID._serialized_end=1362
+ _REQUEST_SESSIONUPDATEPID._serialized_start=1364
+ _REQUEST_SESSIONUPDATEPID._serialized_end=1401
+ _REQUEST_GETCONFIG._serialized_start=1403
+ _REQUEST_GETCONFIG._serialized_end=1429
+ _REQUEST_TEARDOWN._serialized_start=1431
+ _REQUEST_TEARDOWN._serialized_end=1461
+ _REQUEST_VALIDATE._serialized_start=1463
+ _REQUEST_VALIDATE._serialized_end=1533
+ _REQUEST_SET._serialized_start=1535
+ _REQUEST_SET._serialized_end=1554
+ _REQUEST_DELETE._serialized_start=1556
+ _REQUEST_DELETE._serialized_end=1578
+ _REQUEST_DISCARD._serialized_start=1580
+ _REQUEST_DISCARD._serialized_end=1604
+ _REQUEST_SESSIONCHANGED._serialized_start=1606
+ _REQUEST_SESSIONCHANGED._serialized_end=1637
+ _REQUEST_RENAME._serialized_start=1639
+ _REQUEST_RENAME._serialized_end=1692
+ _REQUEST_COPY._serialized_start=1694
+ _REQUEST_COPY._serialized_end=1745
+ _REQUEST_COMMENT._serialized_start=1747
+ _REQUEST_COMMENT._serialized_end=1787
+ _REQUEST_COMMIT._serialized_start=1789
+ _REQUEST_COMMIT._serialized_end=1871
+ _REQUEST_ROLLBACK._serialized_start=1873
+ _REQUEST_ROLLBACK._serialized_end=1901
+ _REQUEST_LOAD._serialized_start=1903
+ _REQUEST_LOAD._serialized_end=1982
+ _REQUEST_MERGE._serialized_start=1984
+ _REQUEST_MERGE._serialized_end=2069
+ _REQUEST_SAVE._serialized_start=2071
+ _REQUEST_SAVE._serialized_end=2134
+ _REQUEST_SHOWCONFIG._serialized_start=2136
+ _REQUEST_SHOWCONFIG._serialized_end=2201
+ _REQUEST_EXISTS._serialized_start=2203
+ _REQUEST_EXISTS._serialized_end=2225
+ _REQUEST_GETVALUE._serialized_start=2227
+ _REQUEST_GETVALUE._serialized_end=2297
+ _REQUEST_GETVALUES._serialized_start=2299
+ _REQUEST_GETVALUES._serialized_end=2370
+ _REQUEST_LISTCHILDREN._serialized_start=2372
+ _REQUEST_LISTCHILDREN._serialized_end=2446
+ _REQUEST_RUNOPMODE._serialized_start=2448
+ _REQUEST_RUNOPMODE._serialized_end=2519
+ _REQUEST_CONFIRM._serialized_start=1799
+ _REQUEST_CONFIRM._serialized_end=1808
+ _REQUEST_ENTERCONFIGURATIONMODE._serialized_start=2532
+ _REQUEST_ENTERCONFIGURATIONMODE._serialized_end=2602
+ _REQUEST_EXITCONFIGURATIONMODE._serialized_start=2604
+ _REQUEST_EXITCONFIGURATIONMODE._serialized_end=2627
+ _REQUEST_RELOADREFTREE._serialized_start=2629
+ _REQUEST_RELOADREFTREE._serialized_end=2664
+ _REQUEST_CONFIGFORMAT._serialized_start=2666
+ _REQUEST_CONFIGFORMAT._serialized_end=2701
+ _REQUEST_OUTPUTFORMAT._serialized_start=2703
+ _REQUEST_OUTPUTFORMAT._serialized_end=2744
+ _REQUESTENVELOPE._serialized_start=2753
+ _REQUESTENVELOPE._serialized_end=2812
+ _RESPONSE._serialized_start=2814
+ _RESPONSE._serialized_end=2897
+# @@protoc_insertion_point(module_scope)
diff --git a/python/vyos/proto/vyconf_proto.py b/python/vyos/proto/vyconf_proto.py
new file mode 100644
index 000000000..ec62a6e35
--- /dev/null
+++ b/python/vyos/proto/vyconf_proto.py
@@ -0,0 +1,379 @@
+from enum import IntEnum
+from dataclasses import dataclass
+from dataclasses import field
+
+class Errnum(IntEnum):
+ SUCCESS = 0
+ FAIL = 1
+ INVALID_PATH = 2
+ INVALID_VALUE = 3
+ COMMIT_IN_PROGRESS = 4
+ CONFIGURATION_LOCKED = 5
+ INTERNAL_ERROR = 6
+ PERMISSION_DENIED = 7
+ PATH_ALREADY_EXISTS = 8
+ UNCOMMITED_CHANGES = 9
+
+class ConfigFormat(IntEnum):
+ CURLY = 0
+ JSON = 1
+
+class OutputFormat(IntEnum):
+ OutPlain = 0
+ OutJSON = 1
+
+@dataclass
+class Prompt:
+ pass
+
+@dataclass
+class SetupSession:
+ ClientPid: int = 0
+ ClientApplication: str = None
+ OnBehalfOf: int = None
+
+@dataclass
+class SessionOfPid:
+ ClientPid: int = 0
+
+@dataclass
+class SessionUpdatePid:
+ ClientPid: int = 0
+
+@dataclass
+class GetConfig:
+ dummy: int = None
+
+@dataclass
+class Teardown:
+ OnBehalfOf: int = None
+
+@dataclass
+class Validate:
+ Path: list[str] = field(default_factory=list)
+ output_format: OutputFormat = None
+
+@dataclass
+class Set:
+ Path: list[str] = field(default_factory=list)
+
+@dataclass
+class Delete:
+ Path: list[str] = field(default_factory=list)
+
+@dataclass
+class Discard:
+ dummy: int = None
+
+@dataclass
+class SessionChanged:
+ dummy: int = None
+
+@dataclass
+class Rename:
+ EditLevel: list[str] = field(default_factory=list)
+ From: str = ""
+ To: str = ""
+
+@dataclass
+class Copy:
+ EditLevel: list[str] = field(default_factory=list)
+ From: str = ""
+ To: str = ""
+
+@dataclass
+class Comment:
+ Path: list[str] = field(default_factory=list)
+ Comment: str = ""
+
+@dataclass
+class Commit:
+ Confirm: bool = None
+ ConfirmTimeout: int = None
+ Comment: str = None
+ DryRun: bool = None
+
+@dataclass
+class Rollback:
+ Revision: int = 0
+
+@dataclass
+class Load:
+ Location: str = ""
+ cached: bool = False
+ format: ConfigFormat = None
+
+@dataclass
+class Merge:
+ Location: str = ""
+ destructive: bool = False
+ format: ConfigFormat = None
+
+@dataclass
+class Save:
+ Location: str = ""
+ format: ConfigFormat = None
+
+@dataclass
+class ShowConfig:
+ Path: list[str] = field(default_factory=list)
+ format: ConfigFormat = None
+
+@dataclass
+class Exists:
+ Path: list[str] = field(default_factory=list)
+
+@dataclass
+class GetValue:
+ Path: list[str] = field(default_factory=list)
+ output_format: OutputFormat = None
+
+@dataclass
+class GetValues:
+ Path: list[str] = field(default_factory=list)
+ output_format: OutputFormat = None
+
+@dataclass
+class ListChildren:
+ Path: list[str] = field(default_factory=list)
+ output_format: OutputFormat = None
+
+@dataclass
+class RunOpMode:
+ Path: list[str] = field(default_factory=list)
+ output_format: OutputFormat = None
+
+@dataclass
+class Confirm:
+ pass
+
+@dataclass
+class EnterConfigurationMode:
+ Exclusive: bool = False
+ OverrideExclusive: bool = False
+
+@dataclass
+class ExitConfigurationMode:
+ pass
+
+@dataclass
+class ReloadReftree:
+ OnBehalfOf: int = None
+
+@dataclass
+class Request:
+ prompt: Prompt = None
+ setup_session: SetupSession = None
+ set: Set = None
+ delete: Delete = None
+ rename: Rename = None
+ copy: Copy = None
+ comment: Comment = None
+ commit: Commit = None
+ rollback: Rollback = None
+ merge: Merge = None
+ save: Save = None
+ show_config: ShowConfig = None
+ exists: Exists = None
+ get_value: GetValue = None
+ get_values: GetValues = None
+ list_children: ListChildren = None
+ run_op_mode: RunOpMode = None
+ confirm: Confirm = None
+ enter_configuration_mode: EnterConfigurationMode = None
+ exit_configuration_mode: ExitConfigurationMode = None
+ validate: Validate = None
+ teardown: Teardown = None
+ reload_reftree: ReloadReftree = None
+ load: Load = None
+ discard: Discard = None
+ session_changed: SessionChanged = None
+ session_of_pid: SessionOfPid = None
+ session_update_pid: SessionUpdatePid = None
+ get_config: GetConfig = None
+
+@dataclass
+class RequestEnvelope:
+ token: str = None
+ request: Request = None
+
+@dataclass
+class Response:
+ status: Errnum = None
+ output: str = None
+ error: str = None
+ warning: str = None
+
+def set_request_prompt(token: str = None):
+ reqi = Prompt ()
+ req = Request(prompt=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_setup_session(token: str = None, client_pid: int = 0, client_application: str = None, on_behalf_of: int = None):
+ reqi = SetupSession (client_pid, client_application, on_behalf_of)
+ req = Request(setup_session=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_session_of_pid(token: str = None, client_pid: int = 0):
+ reqi = SessionOfPid (client_pid)
+ req = Request(session_of_pid=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_session_update_pid(token: str = None, client_pid: int = 0):
+ reqi = SessionUpdatePid (client_pid)
+ req = Request(session_update_pid=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_get_config(token: str = None, dummy: int = None):
+ reqi = GetConfig (dummy)
+ req = Request(get_config=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_teardown(token: str = None, on_behalf_of: int = None):
+ reqi = Teardown (on_behalf_of)
+ req = Request(teardown=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_validate(token: str = None, path: list[str] = [], output_format: OutputFormat = None):
+ reqi = Validate (path, output_format)
+ req = Request(validate=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_set(token: str = None, path: list[str] = []):
+ reqi = Set (path)
+ req = Request(set=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_delete(token: str = None, path: list[str] = []):
+ reqi = Delete (path)
+ req = Request(delete=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_discard(token: str = None, dummy: int = None):
+ reqi = Discard (dummy)
+ req = Request(discard=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_session_changed(token: str = None, dummy: int = None):
+ reqi = SessionChanged (dummy)
+ req = Request(session_changed=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_rename(token: str = None, edit_level: list[str] = [], from_: str = "", to: str = ""):
+ reqi = Rename (edit_level, from_, to)
+ req = Request(rename=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_copy(token: str = None, edit_level: list[str] = [], from_: str = "", to: str = ""):
+ reqi = Copy (edit_level, from_, to)
+ req = Request(copy=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_comment(token: str = None, path: list[str] = [], comment: str = ""):
+ reqi = Comment (path, comment)
+ req = Request(comment=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_commit(token: str = None, confirm: bool = None, confirm_timeout: int = None, comment: str = None, dry_run: bool = None):
+ reqi = Commit (confirm, confirm_timeout, comment, dry_run)
+ req = Request(commit=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_rollback(token: str = None, revision: int = 0):
+ reqi = Rollback (revision)
+ req = Request(rollback=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_load(token: str = None, location: str = "", cached: bool = False, format: ConfigFormat = None):
+ reqi = Load (location, cached, format)
+ req = Request(load=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_merge(token: str = None, location: str = "", destructive: bool = False, format: ConfigFormat = None):
+ reqi = Merge (location, destructive, format)
+ req = Request(merge=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_save(token: str = None, location: str = "", format: ConfigFormat = None):
+ reqi = Save (location, format)
+ req = Request(save=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_show_config(token: str = None, path: list[str] = [], format: ConfigFormat = None):
+ reqi = ShowConfig (path, format)
+ req = Request(show_config=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_exists(token: str = None, path: list[str] = []):
+ reqi = Exists (path)
+ req = Request(exists=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_get_value(token: str = None, path: list[str] = [], output_format: OutputFormat = None):
+ reqi = GetValue (path, output_format)
+ req = Request(get_value=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_get_values(token: str = None, path: list[str] = [], output_format: OutputFormat = None):
+ reqi = GetValues (path, output_format)
+ req = Request(get_values=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_list_children(token: str = None, path: list[str] = [], output_format: OutputFormat = None):
+ reqi = ListChildren (path, output_format)
+ req = Request(list_children=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_run_op_mode(token: str = None, path: list[str] = [], output_format: OutputFormat = None):
+ reqi = RunOpMode (path, output_format)
+ req = Request(run_op_mode=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_confirm(token: str = None):
+ reqi = Confirm ()
+ req = Request(confirm=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_enter_configuration_mode(token: str = None, exclusive: bool = False, override_exclusive: bool = False):
+ reqi = EnterConfigurationMode (exclusive, override_exclusive)
+ req = Request(enter_configuration_mode=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_exit_configuration_mode(token: str = None):
+ reqi = ExitConfigurationMode ()
+ req = Request(exit_configuration_mode=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
+
+def set_request_reload_reftree(token: str = None, on_behalf_of: int = None):
+ reqi = ReloadReftree (on_behalf_of)
+ req = Request(reload_reftree=reqi)
+ req_env = RequestEnvelope(token, req)
+ return req_env
diff --git a/python/vyos/qos/__init__.py b/python/vyos/qos/__init__.py
index a2980ccde..4bffda2d2 100644
--- a/python/vyos/qos/__init__.py
+++ b/python/vyos/qos/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/qos/base.py b/python/vyos/qos/base.py
index b477b5b5e..487249714 100644
--- a/python/vyos/qos/base.py
+++ b/python/vyos/qos/base.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/qos/cake.py b/python/vyos/qos/cake.py
index ca5a26917..05a737649 100644
--- a/python/vyos/qos/cake.py
+++ b/python/vyos/qos/cake.py
@@ -1,4 +1,4 @@
-# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -54,7 +54,16 @@ class CAKE(QoSBase):
f'Invalid flow isolation parameter: {config["flow_isolation"]}'
)
+ if 'ack_filter' in config:
+ if 'aggressive' in config['ack_filter']:
+ tmp += ' ack-filter-aggressive'
+ else:
+ tmp += ' ack-filter'
+ else:
+ tmp += ' no-ack-filter'
+
tmp += ' nat' if 'flow_isolation_nat' in config else ' nonat'
+ tmp += ' no-split-gso' if 'no_split_gso' in config else ' split-gso'
self._cmd(tmp)
diff --git a/python/vyos/qos/droptail.py b/python/vyos/qos/droptail.py
index 427d43d19..223ab1e64 100644
--- a/python/vyos/qos/droptail.py
+++ b/python/vyos/qos/droptail.py
@@ -1,4 +1,4 @@
-# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/qos/fairqueue.py b/python/vyos/qos/fairqueue.py
index f41d098fb..8f4fe2d47 100644
--- a/python/vyos/qos/fairqueue.py
+++ b/python/vyos/qos/fairqueue.py
@@ -1,4 +1,4 @@
-# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/qos/fqcodel.py b/python/vyos/qos/fqcodel.py
index cd2340aa2..d574226ef 100644
--- a/python/vyos/qos/fqcodel.py
+++ b/python/vyos/qos/fqcodel.py
@@ -1,4 +1,4 @@
-# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/qos/limiter.py b/python/vyos/qos/limiter.py
index 3f5c11112..dce376d3e 100644
--- a/python/vyos/qos/limiter.py
+++ b/python/vyos/qos/limiter.py
@@ -1,4 +1,4 @@
-# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/qos/netem.py b/python/vyos/qos/netem.py
index 8bdef300b..8fdd75387 100644
--- a/python/vyos/qos/netem.py
+++ b/python/vyos/qos/netem.py
@@ -1,4 +1,4 @@
-# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/qos/priority.py b/python/vyos/qos/priority.py
index 66d27a639..5f373f696 100644
--- a/python/vyos/qos/priority.py
+++ b/python/vyos/qos/priority.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/qos/randomdetect.py b/python/vyos/qos/randomdetect.py
index a3a39da36..63445bb62 100644
--- a/python/vyos/qos/randomdetect.py
+++ b/python/vyos/qos/randomdetect.py
@@ -1,4 +1,4 @@
-# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/qos/ratelimiter.py b/python/vyos/qos/ratelimiter.py
index a4f80a1be..b0d7b3072 100644
--- a/python/vyos/qos/ratelimiter.py
+++ b/python/vyos/qos/ratelimiter.py
@@ -1,4 +1,4 @@
-# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/qos/roundrobin.py b/python/vyos/qos/roundrobin.py
index 509c4069f..d07dc0f52 100644
--- a/python/vyos/qos/roundrobin.py
+++ b/python/vyos/qos/roundrobin.py
@@ -1,4 +1,4 @@
-# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/qos/trafficshaper.py b/python/vyos/qos/trafficshaper.py
index 9f92ccd8b..3840e7d0e 100644
--- a/python/vyos/qos/trafficshaper.py
+++ b/python/vyos/qos/trafficshaper.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/raid.py b/python/vyos/raid.py
index 7fb794817..4ae63a100 100644
--- a/python/vyos/raid.py
+++ b/python/vyos/raid.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/remote.py b/python/vyos/remote.py
index c54fb6031..b73f486c0 100644
--- a/python/vyos/remote.py
+++ b/python/vyos/remote.py
@@ -1,4 +1,4 @@
-# Copyright 2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -22,6 +22,7 @@ import stat
import sys
import tempfile
import urllib.parse
+import gzip
from contextlib import contextmanager
from pathlib import Path
@@ -44,6 +45,7 @@ from vyos.utils.misc import begin
from vyos.utils.process import cmd, rc_cmd
from vyos.version import get_version
from vyos.base import Warning
+from vyos.defaults import directories
CHUNK_SIZE = 8192
@@ -478,3 +480,45 @@ def get_remote_config(urlstring, source_host='', source_port=0):
return f.read()
finally:
os.remove(temp)
+
+
+def get_config_file(file_in: str, file_out: str, source_host='', source_port=0):
+ protocols = ['scp', 'sftp', 'http', 'https', 'ftp', 'tftp']
+ config_dir = directories['config']
+
+ with tempfile.NamedTemporaryFile() as tmp_file:
+ if any(file_in.startswith(f'{x}://') for x in protocols):
+ try:
+ download(
+ tmp_file.name,
+ file_in,
+ check_space=True,
+ source_host='',
+ source_port=0,
+ raise_error=True,
+ )
+ except Exception as e:
+ return e
+ file_name = tmp_file.name
+ else:
+ full_path = os.path.realpath(file_in)
+ if os.path.isfile(full_path):
+ file_in = full_path
+ else:
+ file_in = os.path.join(config_dir, file_in)
+ if not os.path.isfile(file_in):
+ return ValueError(f'No such file {file_in}')
+
+ file_name = file_in
+
+ if file_in.endswith('.gz'):
+ try:
+ with gzip.open(file_name, 'rb') as f_in:
+ with open(file_out, 'wb') as f_out:
+ shutil.copyfileobj(f_in, f_out)
+ except Exception as e:
+ return e
+ else:
+ shutil.copyfile(file_name, file_out)
+
+ return None
diff --git a/python/vyos/snmpv3_hashgen.py b/python/vyos/snmpv3_hashgen.py
index 324c3274d..57dba07a0 100644
--- a/python/vyos/snmpv3_hashgen.py
+++ b/python/vyos/snmpv3_hashgen.py
@@ -1,4 +1,4 @@
-# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/system/__init__.py b/python/vyos/system/__init__.py
index 0c91330ba..42af8e3e8 100644
--- a/python/vyos/system/__init__.py
+++ b/python/vyos/system/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/system/compat.py b/python/vyos/system/compat.py
index d35bddea2..23a34d38a 100644
--- a/python/vyos/system/compat.py
+++ b/python/vyos/system/compat.py
@@ -1,4 +1,4 @@
-# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/system/disk.py b/python/vyos/system/disk.py
index c8908cd5c..268a3b195 100644
--- a/python/vyos/system/disk.py
+++ b/python/vyos/system/disk.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/system/grub.py b/python/vyos/system/grub.py
index de8303ee2..0f04fa5e9 100644
--- a/python/vyos/system/grub.py
+++ b/python/vyos/system/grub.py
@@ -1,4 +1,4 @@
-# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/system/grub_util.py b/python/vyos/system/grub_util.py
index ad95bb4f9..e534334e6 100644
--- a/python/vyos/system/grub_util.py
+++ b/python/vyos/system/grub_util.py
@@ -1,4 +1,4 @@
-# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/system/image.py b/python/vyos/system/image.py
index aae52e770..ed8a96fbb 100644
--- a/python/vyos/system/image.py
+++ b/python/vyos/system/image.py
@@ -1,4 +1,4 @@
-# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/system/raid.py b/python/vyos/system/raid.py
index 5b33d34da..c03764ad1 100644
--- a/python/vyos/system/raid.py
+++ b/python/vyos/system/raid.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/template.py b/python/vyos/template.py
index d79e1183f..824d42136 100755
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -36,6 +36,7 @@ DEFAULT_TEMPLATE_DIR = directories["templates"]
# Holds template filters registered via register_filter()
_FILTERS = {}
_TESTS = {}
+_CLEVER_FUNCTIONS = {}
# reuse Environments with identical settings to improve performance
@functools.lru_cache(maxsize=2)
@@ -58,6 +59,7 @@ def _get_environment(location=None):
)
env.filters.update(_FILTERS)
env.tests.update(_TESTS)
+ env.globals.update(_CLEVER_FUNCTIONS)
return env
@@ -77,7 +79,7 @@ def register_filter(name, func=None):
"Filters can only be registered before rendering the first template"
)
if name in _FILTERS:
- raise ValueError(f"A filter with name {name!r} was registered already")
+ raise ValueError(f"A filter with name {name!r} was already registered")
_FILTERS[name] = func
return func
@@ -97,10 +99,30 @@ def register_test(name, func=None):
"Tests can only be registered before rendering the first template"
)
if name in _TESTS:
- raise ValueError(f"A test with name {name!r} was registered already")
+ raise ValueError(f"A test with name {name!r} was already registered")
_TESTS[name] = func
return func
+def register_clever_function(name, func=None):
+ """Register a function to be available as test in templates under given name.
+
+ It can also be used as a decorator, see below in this module for examples.
+
+ :raise RuntimeError:
+ when trying to register a test after a template has been rendered already
+ :raise ValueError: when trying to register a name which was taken already
+ """
+ if func is None:
+ return functools.partial(register_clever_function, name)
+ if _get_environment.cache_info().currsize:
+ raise RuntimeError(
+ "Clever functions can only be registered before rendering the" \
+ "first template")
+ if name in _CLEVER_FUNCTIONS:
+ raise ValueError(f"A clever function with name {name!r} was already "\
+ "registered")
+ _CLEVER_FUNCTIONS[name] = func
+ return func
def render_to_string(template, content, formater=None, location=None):
"""Render a template from the template directory, raise on any errors.
@@ -150,6 +172,8 @@ def render(
# As we are opening the file with 'w', we are performing the rendering before
# calling open() to not accidentally erase the file if rendering fails
rendered = render_to_string(template, content, formater, location)
+ # Remove any trailing character and always add a new line at the end
+ rendered = rendered.rstrip() + "\n"
# Write to file
with open(destination, "w") as file:
@@ -558,6 +582,10 @@ def snmp_auth_oid(type):
}
return OIDs[type]
+@register_filter('quoted_join')
+def quoted_join(input_list, join_str, quote='"'):
+ return str(join_str).join(f'{quote}{elem}{quote}' for elem in input_list)
+
@register_filter('nft_action')
def nft_action(vyos_action):
if vyos_action == 'accept':
@@ -650,6 +678,29 @@ def nft_nested_group(out_list, includes, groups, key):
add_includes(name)
return out_list
+@register_filter('nft_accept_invalid')
+def nft_accept_invalid(ether_type):
+ ether_type_mapping = {
+ 'dhcp': 'udp sport 67 udp dport 68',
+ 'arp': 'arp',
+ 'pppoe-discovery': '0x8863',
+ 'pppoe': '0x8864',
+ '802.1q': '8021q',
+ '802.1ad': '8021ad',
+ 'wol': '0x0842',
+ }
+ if ether_type not in ether_type_mapping:
+ raise RuntimeError(f'Ethernet type "{ether_type}" not found in ' \
+ 'available ethernet types!')
+ out = 'ct state invalid '
+
+ if ether_type != 'dhcp':
+ out += 'ether type '
+
+ out += f'{ether_type_mapping[ether_type]} counter accept'
+
+ return out
+
@register_filter('nat_rule')
def nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
from vyos.nat import parse_nat_rule
@@ -704,7 +755,7 @@ def conntrack_rule(rule_conf, rule_id, action, ipv6=False):
if port[0] == '!':
operator = '!='
port = port[1:]
- output.append(f'th {prefix}port {operator} {port}')
+ output.append(f'th {prefix}port {operator} {{ {port} }}')
if 'group' in side_conf:
group = side_conf['group']
@@ -1050,3 +1101,39 @@ def vyos_defined(value, test_value=None, var_type=None):
else:
# Valid value and is matching optional argument if provided - return true
return True
+
+@register_clever_function('get_default_port')
+def get_default_port(service):
+ """
+ Jinja2 plugin to retrieve common service port number from vyos.defaults
+ class from a Jinja2 template. This removes the need to hardcode, or pass in
+ the data using the general dictionary.
+
+ Added to remove code complexity and make it easier to read.
+
+ Example:
+ {{ get_default_port('certbot_haproxy') }}
+ """
+ from vyos.defaults import internal_ports
+ if service not in internal_ports:
+ raise RuntimeError(f'Service "{service}" not found in internal ' \
+ 'vyos.defaults.internal_ports dict!')
+ return internal_ports[service]
+
+@register_clever_function('get_default_config_file')
+def get_default_config_file(filename):
+ """
+ Jinja2 plugin to retrieve a common configuration file path from
+ vyos.defaults class from a Jinja2 template. This removes the need to
+ hardcode, or pass in the data using the general dictionary.
+
+ Added to remove code complexity and make it easier to read.
+
+ Example:
+ {{ get_default_config_file('certbot_haproxy') }}
+ """
+ from vyos.defaults import config_files
+ if filename not in config_files:
+ raise RuntimeError(f'Configuration file "{filename}" not found in '\
+ 'internal vyos.defaults.config_files dict!')
+ return config_files[filename]
diff --git a/python/vyos/tpm.py b/python/vyos/tpm.py
index a24f149fd..663490dec 100644
--- a/python/vyos/tpm.py
+++ b/python/vyos/tpm.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2024 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# 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
diff --git a/python/vyos/utils/__init__.py b/python/vyos/utils/__init__.py
index 3759b2125..280cde17f 100644
--- a/python/vyos/utils/__init__.py
+++ b/python/vyos/utils/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/utils/assertion.py b/python/vyos/utils/assertion.py
index c7fa220c3..aa0614743 100644
--- a/python/vyos/utils/assertion.py
+++ b/python/vyos/utils/assertion.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/utils/auth.py b/python/vyos/utils/auth.py
index 5d0e3464a..6e816af71 100644
--- a/python/vyos/utils/auth.py
+++ b/python/vyos/utils/auth.py
@@ -1,6 +1,6 @@
# authutils -- miscelanneous functions for handling passwords and publis keys
#
-# Copyright (C) 2023-2024 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or modify it under the terms of
# the GNU Lesser General Public License as published by the Free Software Foundation;
diff --git a/python/vyos/utils/backend.py b/python/vyos/utils/backend.py
new file mode 100644
index 000000000..d302a2efd
--- /dev/null
+++ b/python/vyos/utils/backend.py
@@ -0,0 +1,94 @@
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+# N.B. the following is a temporary addition for running smoketests under
+# vyconf and is not to be called explicitly, at the risk of catastophe.
+
+# pylint: disable=wrong-import-position
+
+from pathlib import Path
+
+from vyos.utils.io import ask_yes_no
+from vyos.utils.process import call
+from vyos.utils.process import is_systemd_service_active
+
+VYCONF_SENTINEL = '/run/vyconf_backend'
+
+MSG_ENABLE_VYCONF = 'This will enable the vyconf backend for testing. Proceed?'
+MSG_DISABLE_VYCONF = (
+ 'This will restore the legacy backend; it requires a reboot. Proceed?'
+)
+
+# read/set immutable file attribute without popen:
+# https://www.geeklab.info/2021/04/chattr-and-lsattr-in-python/
+import fcntl # pylint: disable=C0411 # noqa: E402
+from array import array # pylint: disable=C0411 # noqa: E402
+
+# FS constants - see /uapi/linux/fs.h in kernel source
+# or <elixir.free-electrons.com/linux/latest/source/include/uapi/linux/fs.h>
+FS_IOC_GETFLAGS = 0x80086601
+FS_IOC_SETFLAGS = 0x40086602
+FS_IMMUTABLE_FL = 0x010
+
+
+def chattri(filename: str, value: bool):
+ with open(filename, 'r') as f:
+ arg = array('L', [0])
+ fcntl.ioctl(f.fileno(), FS_IOC_GETFLAGS, arg, True)
+ if value:
+ arg[0] = arg[0] | FS_IMMUTABLE_FL
+ else:
+ arg[0] = arg[0] & ~FS_IMMUTABLE_FL
+ fcntl.ioctl(f.fileno(), FS_IOC_SETFLAGS, arg, True)
+
+
+def lsattri(filename: str) -> bool:
+ with open(filename, 'r') as f:
+ arg = array('L', [0])
+ fcntl.ioctl(f.fileno(), FS_IOC_GETFLAGS, arg, True)
+ return bool(arg[0] & FS_IMMUTABLE_FL)
+
+
+# End: read/set immutable file attribute without popen
+
+
+def vyconf_backend() -> bool:
+ return Path(VYCONF_SENTINEL).exists() and lsattri(VYCONF_SENTINEL)
+
+
+def set_vyconf_backend(value: bool, no_prompt: bool = False):
+ vyconfd_service = 'vyconfd.service'
+ commitd_service = 'vyos-commitd.service'
+ http_api_service = 'vyos-http-api.service'
+ match value:
+ case True:
+ if vyconf_backend():
+ return
+ if not no_prompt and not ask_yes_no(MSG_ENABLE_VYCONF):
+ return
+ Path(VYCONF_SENTINEL).touch()
+ chattri(VYCONF_SENTINEL, True)
+ call(f'systemctl restart {vyconfd_service}')
+ call(f'systemctl restart {commitd_service}')
+ if is_systemd_service_active(http_api_service):
+ call(f'systemctl restart {http_api_service}')
+ case False:
+ if not vyconf_backend():
+ return
+ if not no_prompt and not ask_yes_no(MSG_DISABLE_VYCONF):
+ return
+ chattri(VYCONF_SENTINEL, False)
+ Path(VYCONF_SENTINEL).unlink()
+ call('/sbin/shutdown -r now')
diff --git a/python/vyos/utils/boot.py b/python/vyos/utils/boot.py
index 708bef14d..f804cd94e 100644
--- a/python/vyos/utils/boot.py
+++ b/python/vyos/utils/boot.py
@@ -1,4 +1,4 @@
-# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/utils/commit.py b/python/vyos/utils/commit.py
index 105aed8c2..4147c7fba 100644
--- a/python/vyos/utils/commit.py
+++ b/python/vyos/utils/commit.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -13,8 +13,13 @@
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+# pylint: disable=import-outside-toplevel
+
+from typing import IO
+
+
def commit_in_progress():
- """ Not to be used in normal op mode scripts! """
+ """Not to be used in normal op mode scripts!"""
# The CStore backend locks the config by opening a file
# The file is not removed after commit, so just checking
@@ -36,7 +41,9 @@ def commit_in_progress():
from vyos.defaults import commit_lock
if getuser() != 'root':
- raise OSError('This functions needs to be run as root to return correct results!')
+ raise OSError(
+ 'This functions needs to be run as root to return correct results!'
+ )
for proc in process_iter():
try:
@@ -45,7 +52,7 @@ def commit_in_progress():
for f in files:
if f.path == commit_lock:
return True
- except NoSuchProcess as err:
+ except NoSuchProcess:
# Process died before we could examine it
pass
# Default case
@@ -53,8 +60,71 @@ def commit_in_progress():
def wait_for_commit_lock():
- """ Not to be used in normal op mode scripts! """
+ """Not to be used in normal op mode scripts!"""
from time import sleep
+
# Very synchronous approach to multiprocessing
while commit_in_progress():
sleep(1)
+
+
+# For transitional compatibility with the legacy commit locking mechanism,
+# we require a lockf/fcntl (POSIX-type) lock, hence the following in place
+# of vyos.utils.locking
+
+
+def acquire_commit_lock_file() -> tuple[IO, str]:
+ import fcntl
+ from pathlib import Path
+ from vyos.defaults import commit_lock
+
+ try:
+ # pylint: disable=consider-using-with
+ lock_fd = Path(commit_lock).open('w')
+ except IOError as e:
+ out = f'Critical error opening commit lock file {e}'
+ return None, out
+
+ try:
+ fcntl.lockf(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ return lock_fd, ''
+ except IOError:
+ out = 'Configuration system locked by another commit in progress'
+ lock_fd.close()
+ return None, out
+
+
+def release_commit_lock_file(file_descr):
+ import fcntl
+
+ if file_descr is None:
+ return
+ fcntl.lockf(file_descr, fcntl.LOCK_UN)
+ file_descr.close()
+
+
+def call_commit_hooks(which: str):
+ import re
+ import os
+ from pathlib import Path
+ from vyos.defaults import commit_hooks
+ from vyos.utils.process import rc_cmd
+
+ if which not in list(commit_hooks):
+ raise ValueError(f'no entry {which} in commit_hooks')
+
+ hook_dir = commit_hooks[which]
+ file_list = list(Path(hook_dir).glob('*'))
+ regex = re.compile('^[a-zA-Z0-9._-]+$')
+ hook_list = sorted([str(f) for f in file_list if regex.match(f.name)])
+ err = False
+ out = ''
+ for runf in hook_list:
+ try:
+ e, o = rc_cmd(runf)
+ except FileNotFoundError:
+ continue
+ err = err | bool(e)
+ out = out + o
+
+ return out, int(err)
diff --git a/python/vyos/utils/config.py b/python/vyos/utils/config.py
index deda13c13..1f067e91e 100644
--- a/python/vyos/utils/config.py
+++ b/python/vyos/utils/config.py
@@ -1,4 +1,4 @@
-# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/utils/configfs.py b/python/vyos/utils/configfs.py
index 8617f0129..307e1446c 100644
--- a/python/vyos/utils/configfs.py
+++ b/python/vyos/utils/configfs.py
@@ -1,4 +1,4 @@
-# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/utils/convert.py b/python/vyos/utils/convert.py
index 2f587405d..ea07f9514 100644
--- a/python/vyos/utils/convert.py
+++ b/python/vyos/utils/convert.py
@@ -1,4 +1,4 @@
-# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/utils/cpu.py b/python/vyos/utils/cpu.py
index 8ace77d15..0f47123a4 100644
--- a/python/vyos/utils/cpu.py
+++ b/python/vyos/utils/cpu.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2022-2024 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -26,6 +26,7 @@ It has special cases for x86_64 and MAY work correctly on other architectures,
but nothing is certain.
"""
+import os
import re
def _read_cpuinfo():
@@ -114,3 +115,8 @@ def get_available_cpus():
out = json.loads(cmd('lscpu --extended -b --json'))
return out['cpus']
+
+
+def get_half_cpus():
+ """ return 1/2 of the numbers of available CPUs """
+ return max(1, os.cpu_count() // 2)
diff --git a/python/vyos/utils/dict.py b/python/vyos/utils/dict.py
index 1a7a6b96f..e6ef943c6 100644
--- a/python/vyos/utils/dict.py
+++ b/python/vyos/utils/dict.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/utils/disk.py b/python/vyos/utils/disk.py
index d4271ebe1..b822badde 100644
--- a/python/vyos/utils/disk.py
+++ b/python/vyos/utils/disk.py
@@ -1,4 +1,4 @@
-# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/utils/error.py b/python/vyos/utils/error.py
index 8d4709bff..75ad813f3 100644
--- a/python/vyos/utils/error.py
+++ b/python/vyos/utils/error.py
@@ -1,4 +1,4 @@
-# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/utils/file.py b/python/vyos/utils/file.py
index eaebb57a3..31c2361df 100644
--- a/python/vyos/utils/file.py
+++ b/python/vyos/utils/file.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -28,22 +28,28 @@ def file_is_persistent(path):
absolute = os.path.abspath(os.path.dirname(path))
return re.match(location,absolute)
-def read_file(fname, defaultonfailure=None):
+def read_file(fname, defaultonfailure=None, sudo=False):
"""
read the content of a file, stripping any end characters (space, newlines)
should defaultonfailure be not None, it is returned on failure to read
"""
try:
- """ Read a file to string """
- with open(fname, 'r') as f:
- data = f.read().strip()
- return data
+ # Some files can only be read by root - emulate sudo cat call
+ if sudo:
+ from vyos.utils.process import cmd
+ data = cmd(['sudo', 'cat', fname])
+ else:
+ # If not sudo, just read the file
+ with open(fname, 'r') as f:
+ data = f.read()
+ return data.strip()
except Exception as e:
if defaultonfailure is not None:
return defaultonfailure
raise e
-def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=None, append=False):
+def write_file(fname, data, defaultonfailure=None, user=None, group=None,
+ mode=None, append=False, trailing_newline=False):
"""
Write content of data to given fname, should defaultonfailure be not None,
it is returned on failure to read.
@@ -60,6 +66,9 @@ def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=N
bytes = 0
with open(fname, 'w' if not append else 'a') as f:
bytes = f.write(data)
+ if trailing_newline and not data.endswith('\n'):
+ f.write('\n')
+ bytes += 1
chown(fname, user, group)
chmod(fname, mode)
return bytes
diff --git a/python/vyos/utils/io.py b/python/vyos/utils/io.py
index 205210b66..0883376d1 100644
--- a/python/vyos/utils/io.py
+++ b/python/vyos/utils/io.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/utils/kernel.py b/python/vyos/utils/kernel.py
index 05eac8a6a..4d8544670 100644
--- a/python/vyos/utils/kernel.py
+++ b/python/vyos/utils/kernel.py
@@ -1,4 +1,4 @@
-# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/utils/list.py b/python/vyos/utils/list.py
index 63ef720ab..931084e7c 100644
--- a/python/vyos/utils/list.py
+++ b/python/vyos/utils/list.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/utils/locking.py b/python/vyos/utils/locking.py
index 63cb1a816..f4cd6fd41 100644
--- a/python/vyos/utils/locking.py
+++ b/python/vyos/utils/locking.py
@@ -1,4 +1,4 @@
-# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/utils/misc.py b/python/vyos/utils/misc.py
index d82655914..0ffd82696 100644
--- a/python/vyos/utils/misc.py
+++ b/python/vyos/utils/misc.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py
index 2f666f0ee..2182642dd 100644
--- a/python/vyos/utils/network.py
+++ b/python/vyos/utils/network.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -256,40 +256,60 @@ def mac2eui64(mac, prefix=None):
except: # pylint: disable=bare-except
return
-def check_port_availability(ipaddress, port, protocol):
+def check_port_availability(address: str=None, port: int=0, protocol: str='tcp') -> bool:
"""
- Check if port is available and not used by any service
- Return False if a port is busy or IP address does not exists
+ Check if given port is available and not used by any service.
+
Should be used carefully for services that can start listening
dynamically, because IP address may be dynamic too
+
+ Args:
+ address: IPv4 or IPv6 address - if None, checks on all interfaces
+ port: TCP/UDP port number.
+
+
+ Returns:
+ False if a port is busy or IP address does not exists
+ True if a port is free and IP address exists
"""
- from socketserver import TCPServer, UDPServer
+ import socket
from ipaddress import ip_address
+ # treat None as "any address"
+ address = address or '::'
+
# verify arguments
try:
- ipaddress = ip_address(ipaddress).compressed
- except:
- raise ValueError(f'The {ipaddress} is not a valid IPv4 or IPv6 address')
+ address = ip_address(address).compressed
+ except ValueError:
+ raise ValueError(f'{address} is not a valid IPv4 or IPv6 address')
if port not in range(1, 65536):
- raise ValueError(f'The port number {port} is not in the 1-65535 range')
+ raise ValueError(f'Port {port} is not in range 1-65535')
if protocol not in ['tcp', 'udp']:
- raise ValueError(f'The protocol {protocol} is not supported. Only tcp and udp are allowed')
+ raise ValueError(f'{protocol} is not supported - only tcp and udp are allowed')
- # check port availability
+ protocol = socket.SOCK_STREAM if protocol == 'tcp' else socket.SOCK_DGRAM
try:
- if protocol == 'tcp':
- server = TCPServer((ipaddress, port), None, bind_and_activate=True)
- if protocol == 'udp':
- server = UDPServer((ipaddress, port), None, bind_and_activate=True)
- server.server_close()
- except Exception as e:
- # errno.h:
- #define EADDRINUSE 98 /* Address already in use */
- if e.errno == 98:
+ addr_info = socket.getaddrinfo(address, port, socket.AF_UNSPEC, protocol)
+ except socket.gaierror as e:
+ print(f'Invalid address: {address}')
+ return False
+
+ for family, socktype, proto, canonname, sockaddr in addr_info:
+ try:
+ with socket.socket(family, socktype, proto) as s:
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ s.bind(sockaddr)
+ # port is free to use
+ return True
+ except OSError:
+ # port is already in use
return False
- return True
+ # if we reach this point, no socket was tested and we assume the port is
+ # already in use - better safe then sorry
+ return False
+
def is_listen_port_bind_service(port: int, service: str) -> bool:
"""Check if listen port bound to expected program name
@@ -396,6 +416,21 @@ def is_wireguard_key_pair(private_key: str, public_key:str) -> bool:
else:
return False
+def get_wireguard_peers(ifname: str) -> list:
+ """
+ Return list of configured Wireguard peers for interface
+ :param ifname: Interface name
+ :type ifname: str
+ :return: list of public keys
+ :rtype: list
+ """
+ if not interface_exists(ifname):
+ return []
+
+ from vyos.utils.process import cmd
+ peers = cmd(f'wg show {ifname} peers')
+ return peers.splitlines()
+
def is_subnet_connected(subnet, primary=False):
"""
Verify is the given IPv4/IPv6 subnet is connected to any interface on this
@@ -615,3 +650,19 @@ def is_valid_ipv4_address_or_range(addr: str) -> bool:
return ip_network(addr).version == 4
except:
return False
+
+def is_valid_ipv6_address_or_range(addr: str) -> bool:
+ """
+ Validates if the provided address is a valid IPv4, CIDR or IPv4 range
+ :param addr: address to test
+ :return: bool: True if provided address is valid
+ """
+ from ipaddress import ip_network
+ try:
+ if '-' in addr: # If we are checking a range, validate both address's individually
+ split = addr.split('-')
+ return is_valid_ipv6_address_or_range(split[0]) and is_valid_ipv6_address_or_range(split[1])
+ else:
+ return ip_network(addr).version == 6
+ except:
+ return False
diff --git a/python/vyos/utils/permission.py b/python/vyos/utils/permission.py
index d938b494f..efd44bfeb 100644
--- a/python/vyos/utils/permission.py
+++ b/python/vyos/utils/permission.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/utils/process.py b/python/vyos/utils/process.py
index 121b6e240..86a2747af 100644
--- a/python/vyos/utils/process.py
+++ b/python/vyos/utils/process.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -14,6 +14,7 @@
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
import os
+import shlex
from subprocess import Popen
from subprocess import PIPE
@@ -21,20 +22,17 @@ from subprocess import STDOUT
from subprocess import DEVNULL
-def get_wrapper(vrf, netns, auth):
- wrapper = ''
+def get_wrapper(vrf, netns):
+ wrapper = None
if vrf:
- wrapper = f'ip vrf exec {vrf} '
+ wrapper = ['ip', 'vrf', 'exec', vrf]
elif netns:
- wrapper = f'ip netns exec {netns} '
- if auth:
- wrapper = f'{auth} {wrapper}'
+ wrapper = ['ip', 'netns', 'exec', netns]
return wrapper
def popen(command, flag='', shell=None, input=None, timeout=None, env=None,
- stdout=PIPE, stderr=PIPE, decode='utf-8', auth='', vrf=None,
- netns=None):
+ stdout=PIPE, stderr=PIPE, decode='utf-8', vrf=None, netns=None):
"""
popen is a wrapper helper around subprocess.Popen
with it default setting it will return a tuple (out, err)
@@ -75,28 +73,33 @@ def popen(command, flag='', shell=None, input=None, timeout=None, env=None,
if not debug.enabled(flag):
flag = 'command'
+ use_shell = shell
+ stdin = None
+ if shell is None:
+ use_shell = False
+ if ' ' in command:
+ use_shell = True
+ if env:
+ use_shell = True
+
# Must be run as root to execute command in VRF or network namespace
+ wrapper = get_wrapper(vrf, netns)
if vrf or netns:
if os.getuid() != 0:
raise OSError(
'Permission denied: cannot execute commands in VRF and netns contexts as an unprivileged user'
)
- wrapper = get_wrapper(vrf, netns, auth)
- command = f'{wrapper} {command}' if wrapper else command
+ if use_shell:
+ command = f'{shlex.join(wrapper)} {command}'
+ else:
+ if type(command) is not list:
+ command = [command]
+ command = wrapper + command
- cmd_msg = f"cmd '{command}'"
+ cmd_msg = f"cmd '{command}'" if use_shell else f"cmd '{shlex.join(command)}'"
debug.message(cmd_msg, flag)
- use_shell = shell
- stdin = None
- if shell is None:
- use_shell = False
- if ' ' in command:
- use_shell = True
- if env:
- use_shell = True
-
if input:
stdin = PIPE
input = input.encode() if type(input) is str else input
@@ -155,7 +158,7 @@ def run(command, flag='', shell=None, input=None, timeout=None, env=None,
def cmd(command, flag='', shell=None, input=None, timeout=None, env=None,
stdout=PIPE, stderr=PIPE, decode='utf-8', raising=None, message='',
- expect=[0], auth='', vrf=None, netns=None):
+ expect=[0], vrf=None, netns=None):
"""
A wrapper around popen, which returns the stdout and
will raise the error code of a command
@@ -171,12 +174,11 @@ def cmd(command, flag='', shell=None, input=None, timeout=None, env=None,
input=input, timeout=timeout,
env=env, shell=shell,
decode=decode,
- auth=auth,
vrf=vrf,
netns=netns,
)
if code not in expect:
- wrapper = get_wrapper(vrf, netns, auth='')
+ wrapper = get_wrapper(vrf, netns)
command = f'{wrapper} {command}'
feedback = message + '\n' if message else ''
feedback += f'failed to run command: {command}\n'
diff --git a/python/vyos/utils/serial.py b/python/vyos/utils/serial.py
index b646f881e..68aad676e 100644
--- a/python/vyos/utils/serial.py
+++ b/python/vyos/utils/serial.py
@@ -1,4 +1,4 @@
-# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/utils/session.py b/python/vyos/utils/session.py
new file mode 100644
index 000000000..bc5240fc7
--- /dev/null
+++ b/python/vyos/utils/session.py
@@ -0,0 +1,25 @@
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+# pylint: disable=import-outside-toplevel
+
+
+def in_config_session():
+ """Vyatta bash completion uses the following environment variable for
+ indication of the config mode environment, independent of legacy backend
+ initialization of Cstore"""
+ from os import environ
+
+ return '_OFR_CONFIGURE' in environ
diff --git a/python/vyos/utils/strip_config.py b/python/vyos/utils/strip_config.py
index 7a9c78c9f..17f6867cb 100644
--- a/python/vyos/utils/strip_config.py
+++ b/python/vyos/utils/strip_config.py
@@ -1,6 +1,6 @@
#!/usr/bin/python3
#
-# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/utils/system.py b/python/vyos/utils/system.py
index 6c112334b..e2197daf2 100644
--- a/python/vyos/utils/system.py
+++ b/python/vyos/utils/system.py
@@ -1,4 +1,4 @@
-# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/utils/vti_updown_db.py b/python/vyos/utils/vti_updown_db.py
index b491fc6f2..f4dd24007 100644
--- a/python/vyos/utils/vti_updown_db.py
+++ b/python/vyos/utils/vti_updown_db.py
@@ -1,4 +1,4 @@
-# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/version.py b/python/vyos/version.py
index 86e96d0ec..01986e4da 100644
--- a/python/vyos/version.py
+++ b/python/vyos/version.py
@@ -1,4 +1,4 @@
-# Copyright 2017-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/vyconf_session.py b/python/vyos/vyconf_session.py
index 506095625..3cf5fb4e3 100644
--- a/python/vyos/vyconf_session.py
+++ b/python/vyos/vyconf_session.py
@@ -1,4 +1,4 @@
-# Copyright 2025 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -15,8 +15,8 @@
#
#
+import os
import tempfile
-import shutil
from functools import wraps
from typing import Type
@@ -24,26 +24,88 @@ from vyos.proto import vyconf_client
from vyos.migrate import ConfigMigrate
from vyos.migrate import ConfigMigrateError
from vyos.component_version import append_system_version
+from vyos.utils.session import in_config_session
+from vyos.proto.vyconf_proto import Errnum
+from vyos.utils.commit import acquire_commit_lock_file
+from vyos.utils.commit import release_commit_lock_file
+from vyos.utils.commit import call_commit_hooks
+from vyos.remote import get_config_file
-def output(o):
- out = ''
- for res in (o.output, o.error, o.warning):
- if res is not None:
- out = out + res
- return out
+class VyconfSessionError(Exception):
+ pass
class VyconfSession:
- def __init__(self, token: str = None, on_error: Type[Exception] = None):
+ def __init__(
+ self, token: str = None, pid: int = None, on_error: Type[Exception] = None
+ ):
+ self.pid = os.getpid() if pid is None else pid
if token is None:
- out = vyconf_client.send_request('setup_session')
+ # CLI applications with arg pid=getppid() allow coordination
+ # with the ambient session; other uses (such as ConfigSession)
+ # may default to self pid
+ out = vyconf_client.send_request('session_of_pid', client_pid=self.pid)
+ if out.output is None:
+ out = vyconf_client.send_request('setup_session', client_pid=self.pid)
self.__token = out.output
else:
+ out = vyconf_client.send_request(
+ 'session_update_pid', token=token, client_pid=self.pid
+ )
+ if out.status:
+ raise ValueError(f'No existing session for token: {token}')
self.__token = token
+ self.in_config_session = in_config_session()
+ if self.in_config_session:
+ out = vyconf_client.send_request(
+ 'enter_configuration_mode', token=self.__token
+ )
+ if out.status:
+ raise VyconfSessionError(self.output(out))
+
self.on_error = on_error
+ def __del__(self):
+ if not self.in_config_session:
+ self.teardown()
+
+ def teardown(self):
+ vyconf_client.send_request('teardown', token=self.__token)
+
+ def exit_config_mode(self):
+ if self.session_changed():
+ return 'Uncommited changes', Errnum.UNCOMMITED_CHANGES
+ out = vyconf_client.send_request('exit_configuration_mode', token=self.__token)
+ return self.output(out), out.status
+
+ def in_session(self) -> bool:
+ return self.in_config_session
+
+ def session_changed(self) -> bool:
+ out = vyconf_client.send_request('session_changed', token=self.__token)
+ return not bool(out.status)
+
+ def get_config(self):
+ out = vyconf_client.send_request('get_config', token=self.__token)
+ if out.status:
+ raise VyconfSessionError(self.output(out))
+ return out.output
+
+ @staticmethod
+ def config_mode(f):
+ @wraps(f)
+ def wrapped(self, *args, **kwargs):
+ msg = 'operation not available outside of config mode'
+ if not self.in_config_session:
+ if self.on_error is None:
+ raise VyconfSessionError(msg)
+ raise self.on_error(msg)
+ return f(self, *args, **kwargs)
+
+ return wrapped
+
@staticmethod
def raise_exception(f):
@wraps(f)
@@ -57,67 +119,118 @@ class VyconfSession:
return wrapped
+ @staticmethod
+ def output(o):
+ out = ''
+ for res in (o.output, o.error, o.warning):
+ if res is not None:
+ out = out + res
+ return out
+
@raise_exception
+ @config_mode
def set(self, path: list[str]) -> tuple[str, int]:
out = vyconf_client.send_request('set', token=self.__token, path=path)
- return output(out), out.status
+ return self.output(out), out.status
@raise_exception
+ @config_mode
def delete(self, path: list[str]) -> tuple[str, int]:
out = vyconf_client.send_request('delete', token=self.__token, path=path)
- return output(out), out.status
+ return self.output(out), out.status
@raise_exception
+ @config_mode
def commit(self) -> tuple[str, int]:
+ if not self.session_changed():
+ out = 'No changes to commit'
+ return out, 0
+
+ lock_fd, out = acquire_commit_lock_file()
+ if lock_fd is None:
+ return out, Errnum.COMMIT_IN_PROGRESS
+
+ pre_out, _ = call_commit_hooks('pre')
out = vyconf_client.send_request('commit', token=self.__token)
- return output(out), out.status
+ os.environ['COMMIT_STATUS'] = 'FAILURE' if out.status else 'SUCCESS'
+ post_out, _ = call_commit_hooks('post')
+
+ release_commit_lock_file(lock_fd)
+
+ return pre_out + self.output(out) + post_out, out.status
@raise_exception
+ @config_mode
def discard(self) -> tuple[str, int]:
out = vyconf_client.send_request('discard', token=self.__token)
- return output(out), out.status
+ return self.output(out), out.status
- def session_changed(self) -> bool:
- out = vyconf_client.send_request('session_changed', token=self.__token)
- return not bool(out.status)
+ @raise_exception
+ @config_mode
+ def load_config(
+ self, file_name: str, migrate: bool = False, cached: bool = False
+ ) -> tuple[str, int]:
+ # pylint: disable=consider-using-with
+ file_path = tempfile.NamedTemporaryFile(delete=False).name
+ err = get_config_file(file_name, file_path)
+ if err:
+ os.remove(file_path)
+ return str(err), Errnum.INVALID_VALUE
+ if not cached:
+ if migrate:
+ config_migrate = ConfigMigrate(file_path)
+ try:
+ config_migrate.run()
+ except ConfigMigrateError as e:
+ os.remove(file_path)
+ return repr(e), 1
+
+ out = vyconf_client.send_request(
+ 'load', token=self.__token, location=file_path, cached=cached
+ )
+
+ if not cached:
+ os.remove(file_path)
+
+ return self.output(out), out.status
@raise_exception
- def load_config(self, file: str, migrate: bool = False) -> tuple[str, int]:
+ @config_mode
+ def merge_config(
+ self, file_name: str, migrate: bool = False, destructive: bool = False
+ ) -> tuple[str, int]:
# pylint: disable=consider-using-with
+ file_path = tempfile.NamedTemporaryFile(delete=False).name
+ err = get_config_file(file_name, file_path)
+ if err:
+ os.remove(file_path)
+ return str(err), Errnum.INVALID_VALUE
if migrate:
- tmp = tempfile.NamedTemporaryFile()
- shutil.copy2(file, tmp.name)
- config_migrate = ConfigMigrate(tmp.name)
+ config_migrate = ConfigMigrate(file_path)
try:
config_migrate.run()
except ConfigMigrateError as e:
- tmp.close()
+ os.remove(file_path)
return repr(e), 1
- file = tmp.name
- else:
- tmp = ''
- out = vyconf_client.send_request('load', token=self.__token, location=file)
- if tmp:
- tmp.close()
+ out = vyconf_client.send_request(
+ 'merge', token=self.__token, location=file_path, destructive=destructive
+ )
- return output(out), out.status
+ os.remove(file_path)
+
+ return self.output(out), out.status
@raise_exception
def save_config(self, file: str, append_version: bool = False) -> tuple[str, int]:
out = vyconf_client.send_request('save', token=self.__token, location=file)
if append_version:
append_system_version(file)
- return output(out), out.status
+ return self.output(out), out.status
@raise_exception
def show_config(self, path: list[str] = None) -> tuple[str, int]:
if path is None:
path = []
out = vyconf_client.send_request('show_config', token=self.__token, path=path)
- return output(out), out.status
-
- def __del__(self):
- out = vyconf_client.send_request('teardown', token=self.__token)
- if out.status:
- print(f'Could not tear down session {self.__token}: {output(out)}')
+ return self.output(out), out.status
diff --git a/python/vyos/wanloadbalance.py b/python/vyos/wanloadbalance.py
index 62e109f21..2381f7d1c 100644
--- a/python/vyos/wanloadbalance.py
+++ b/python/vyos/wanloadbalance.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2024 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# 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
diff --git a/python/vyos/xml_ref/__init__.py b/python/vyos/xml_ref/__init__.py
index 99d8432d2..41a25049e 100644
--- a/python/vyos/xml_ref/__init__.py
+++ b/python/vyos/xml_ref/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -14,6 +14,8 @@
# along with this library. If not, see <http://www.gnu.org/licenses/>.
from typing import Optional, Union, TYPE_CHECKING
+from typing import Callable
+from typing import Any
from vyos.xml_ref import definition
from vyos.xml_ref import op_definition
@@ -89,6 +91,7 @@ def from_source(d: dict, path: list) -> bool:
def ext_dict_merge(source: dict, destination: Union[dict, 'ConfigDict']):
return definition.ext_dict_merge(source, destination)
+
def load_op_reference(op_cache=[]):
if op_cache:
return op_cache[0]
@@ -108,5 +111,26 @@ def load_op_reference(op_cache=[]):
return op_xml
-def get_op_ref_path(path: list) -> list[op_definition.PathData]:
- return load_op_reference()._get_op_ref_path(path)
+
+def walk_op_data(func: Callable[[tuple, dict], Any]):
+ return load_op_reference().walk(func)
+
+
+def walk_op_node_data():
+ return load_op_reference().walk_node_data()
+
+
+def lookup_op_data(
+ path: list, tag_values: bool = False, last_node_type: str = ''
+) -> (dict, list[str]):
+ return load_op_reference().lookup(
+ path, tag_values=tag_values, last_node_type=last_node_type
+ )
+
+
+def lookup_op_node_data(
+ path: list, tag_values: bool = False, last_node_type: str = ''
+) -> list[op_definition.NodeData]:
+ return load_op_reference().lookup_node_data(
+ path, tag_values=tag_values, last_node_type=last_node_type
+ )
diff --git a/python/vyos/xml_ref/definition.py b/python/vyos/xml_ref/definition.py
index 4e755ab72..015e7ee6e 100644
--- a/python/vyos/xml_ref/definition.py
+++ b/python/vyos/xml_ref/definition.py
@@ -1,4 +1,4 @@
-# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
diff --git a/python/vyos/xml_ref/generate_cache.py b/python/vyos/xml_ref/generate_cache.py
index 093697993..f0a3ec35b 100755
--- a/python/vyos/xml_ref/generate_cache.py
+++ b/python/vyos/xml_ref/generate_cache.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2023-2024 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# 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
diff --git a/python/vyos/xml_ref/generate_op_cache.py b/python/vyos/xml_ref/generate_op_cache.py
index 95779d066..266c81cd0 100755
--- a/python/vyos/xml_ref/generate_op_cache.py
+++ b/python/vyos/xml_ref/generate_op_cache.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2024-2025 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# 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
@@ -14,10 +14,13 @@
# 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 os
+import io
import re
import sys
-import json
import glob
+import json
+import atexit
from argparse import ArgumentParser
from os.path import join
@@ -25,23 +28,44 @@ from os.path import abspath
from os.path import dirname
from xml.etree import ElementTree as ET
from xml.etree.ElementTree import Element
+from functools import cmp_to_key
from typing import TypeAlias
from typing import Optional
+from op_definition import NodeData
+from op_definition import OpKey # pylint: disable=unused-import # noqa: F401
+from op_definition import OpData # pylint: disable=unused-import # noqa: F401
+from op_definition import key_name
+from op_definition import key_type
+from op_definition import node_data_difference
+from op_definition import get_node_data
+from op_definition import collapse
+
_here = dirname(__file__)
sys.path.append(join(_here, '..'))
-from defaults import directories
-
-from op_definition import PathData
+# pylint: disable=wrong-import-position,wrong-import-order
+from defaults import directories # noqa: E402
-xml_op_cache_json = 'xml_op_cache.json'
-xml_op_tmp = join('/tmp', xml_op_cache_json)
op_ref_cache = abspath(join(_here, 'op_cache.py'))
+op_ref_json = abspath(join(_here, 'op_cache.json'))
OptElement: TypeAlias = Optional[Element]
-DEBUG = False
+
+
+# It is expected that the node_data help txt contained in top-level nodes,
+# shared across files, e.g.'show', will reveal inconsistencies; to list
+# differences, use --check-xml-consistency
+CHECK_XML_CONSISTENCY = False
+err_buf = io.StringIO()
+
+
+def write_err_buf():
+ err_buf.seek(0)
+ out = err_buf.read()
+ print(out)
+ err_buf.close()
def translate_exec(s: str) -> str:
@@ -74,14 +98,58 @@ def translate_op_script(s: str) -> str:
return s
-def insert_node(n: Element, l: list[PathData], path=None) -> None:
- # pylint: disable=too-many-locals,too-many-branches
+def compare_keys(a, b):
+ # pylint: disable=too-many-return-statements
+ match key_type(a), key_type(b):
+ case None, None:
+ if key_name(a) == key_name(b):
+ return 0
+ return -1 if key_name(a) < key_name(b) else 1
+ case None, _:
+ return -1
+ case _, None:
+ return 1
+ case _, _:
+ if key_name(a) == key_name(b):
+ if key_type(a) == key_type(b):
+ return 0
+ return -1 if key_type(a) < key_type(b) else 1
+ return -1 if key_name(a) < key_name(b) else 1
+
+
+def sort_func(obj: dict, key_func):
+ if not obj or not isinstance(obj, dict):
+ return obj
+ k_list = list(obj.keys())
+ if not isinstance(k_list[0], tuple):
+ return obj
+ k_list = sorted(k_list, key=key_func)
+ v_list = map(lambda t: sort_func(obj[t], key_func), k_list)
+ return dict(zip(k_list, v_list))
+
+
+def sort_op_data(obj):
+ key_func = cmp_to_key(compare_keys)
+ return sort_func(obj, key_func)
+
+
+def insert_node(
+ n: Element, d: dict, path: list[str] = None, parent: NodeData = None, file: str = ''
+) -> None:
+ # pylint: disable=too-many-locals,too-many-branches,too-many-statements
prop: OptElement = n.find('properties')
children: OptElement = n.find('children')
command: OptElement = n.find('command')
- # name is not None as required by schema
- name: str = n.get('name', 'schema_error')
+ standalone: OptElement = n.find('standalone')
node_type: str = n.tag
+
+ if node_type == 'virtualTagNode':
+ name = '__virtual_tag'
+ else:
+ name = n.get('name')
+ if not name:
+ raise ValueError("Node name is required for all node types except <virtualTagNode>")
+
if path is None:
path = []
@@ -95,6 +163,16 @@ def insert_node(n: Element, l: list[PathData], path=None) -> None:
if command_text is not None:
command_text = translate_command(command_text, path)
+ try:
+ standalone_command = translate_command(standalone.find('command').text, path)
+ except AttributeError:
+ standalone_command = None
+
+ try:
+ standalone_help_text = translate_command(standalone.find('help').text, path)
+ except AttributeError:
+ standalone_help_text = None
+
comp_help = {}
if prop is not None:
che = prop.findall('completionHelp')
@@ -124,31 +202,49 @@ def insert_node(n: Element, l: list[PathData], path=None) -> None:
if comp_scripts:
comp_help['script'] = comp_scripts
- cur_node_dict = {}
- cur_node_dict['name'] = name
- cur_node_dict['type'] = node_type
- cur_node_dict['comp_help'] = comp_help
- cur_node_dict['help'] = help_text
- cur_node_dict['command'] = command_text
- cur_node_dict['path'] = path
- cur_node_dict['children'] = []
- l.append(cur_node_dict)
+ cur_node_data = NodeData()
+ cur_node_data.name = name
+ cur_node_data.node_type = node_type
+ cur_node_data.comp_help = comp_help
+ cur_node_data.help_text = help_text
+ cur_node_data.command = command_text
+ cur_node_data.standalone_help_text = standalone_help_text
+ cur_node_data.standalone_command = standalone_command
+ cur_node_data.path = path
+ cur_node_data.file = file
+
+ value = {('__node_data', None): cur_node_data}
+ key = (name, node_type)
+
+ cur_value = d.setdefault(key, value)
+
+ if parent and key not in parent.children:
+ parent.children.append(key)
+
+ if CHECK_XML_CONSISTENCY:
+ out = node_data_difference(get_node_data(cur_value), get_node_data(value))
+ if out:
+ err_buf.write(out)
if children is not None:
inner_nodes = children.iterfind('*')
for inner_n in inner_nodes:
inner_path = path[:]
- insert_node(inner_n, cur_node_dict['children'], inner_path)
+ insert_node(inner_n, d[key], inner_path, cur_node_data, file)
-def parse_file(file_path, l):
+def parse_file(file_path, d):
tree = ET.parse(file_path)
root = tree.getroot()
+ file = os.path.basename(file_path)
for n in root.iterfind('*'):
- insert_node(n, l)
+ insert_node(n, d, file=file)
def main():
+ # pylint: disable=global-statement
+ global CHECK_XML_CONSISTENCY
+
parser = ArgumentParser(description='generate dict from xml defintions')
parser.add_argument(
'--xml-dir',
@@ -156,21 +252,58 @@ def main():
required=True,
help='transcluded xml op-mode-definition file',
)
+ parser.add_argument(
+ '--check-xml-consistency',
+ action='store_true',
+ help='check consistency of node data across files',
+ )
+ parser.add_argument(
+ '--check-path-ambiguity',
+ action='store_true',
+ help='attempt to reduce to unique paths, reporting if error',
+ )
+ parser.add_argument(
+ '--select',
+ type=str,
+ help='limit cache to a subset of XML files: "power_ctl | multicast-group | ..."',
+ )
args = vars(parser.parse_args())
+ if args['check_xml_consistency']:
+ CHECK_XML_CONSISTENCY = True
+ atexit.register(write_err_buf)
+
xml_dir = abspath(args['xml_dir'])
- l = []
+ d = {}
+
+ select = args['select']
+ if select:
+ select = [item.strip() for item in select.split('|')]
+
+ for fname in sorted(glob.glob(f'{xml_dir}/*.xml')):
+ file = os.path.basename(fname)
+ if not select or os.path.splitext(file)[0] in select:
+ parse_file(fname, d)
- for fname in glob.glob(f'{xml_dir}/*.xml'):
- parse_file(fname, l)
+ d = sort_op_data(d)
- with open(xml_op_tmp, 'w') as f:
- json.dump(l, f, indent=2)
+ if args['check_path_ambiguity']:
+ # when the following passes without error, return value will be the
+ # full dictionary indexed by str, not tuple
+ res, out, err = collapse(d)
+ if not err:
+ with open(op_ref_json, 'w') as f:
+ json.dump(res, f, indent=2)
+ else:
+ print('Found the following duplicate paths:\n')
+ print(out)
+ sys.exit(1)
with open(op_ref_cache, 'w') as f:
- f.write(f'op_reference = {str(l)}')
+ f.write('from vyos.xml_ref.op_definition import NodeData\n')
+ f.write(f'op_reference = {str(d)}')
if __name__ == '__main__':
diff --git a/python/vyos/xml_ref/op_definition.py b/python/vyos/xml_ref/op_definition.py
index 914f3a105..7b0a45a5b 100644
--- a/python/vyos/xml_ref/op_definition.py
+++ b/python/vyos/xml_ref/op_definition.py
@@ -1,4 +1,4 @@
-# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -13,37 +13,243 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see <http://www.gnu.org/licenses/>.
-from typing import TypedDict
from typing import TypeAlias
-from typing import Optional
from typing import Union
+from typing import Optional
+from typing import Iterator
+from dataclasses import dataclass
+from dataclasses import field
+from dataclasses import fields
+from dataclasses import asdict
+from itertools import filterfalse
+
+
+@dataclass
+class NodeData:
+ # pylint: disable=too-many-instance-attributes
+ name: str = ''
+ node_type: str = 'node'
+ help_text: str = ''
+ comp_help: dict[str, list] = field(default_factory=dict)
+ command: str = ''
+ standalone_help_text: Optional[str] = None
+ standalone_command: Optional[str] = None
+ path: list[str] = field(default_factory=list)
+ file: str = ''
+ children: list[tuple] = field(default_factory=list)
+
+
+OpKey: TypeAlias = tuple[str, str]
+OpData: TypeAlias = dict[OpKey, Union[NodeData, 'OpData']]
+
+
+def key_name(k: OpKey):
+ return k[0]
+
+
+def key_type(k: OpKey):
+ return k[1]
+
+
+def key_names(l: list): # noqa: E741
+ return list(map(lambda t: t[0], l))
+
+
+def keys_of_name(s: str, l: list): # noqa: E741
+ filter(lambda t: t[0] == s, l)
+
+
+def is_tag_node(t: tuple):
+ return t[1] == 'tagNode'
+
+
+def subdict_of_name(s: str, d: dict) -> dict:
+ res = {}
+ for t, v in d.items():
+ if not isinstance(t, tuple):
+ break
+ if key_name(t) == s:
+ res[t] = v
+
+ return res
+
+
+def next_keys(d: dict) -> list:
+ key_set = set()
+ for k in list(d.keys()):
+ if isinstance(d[k], dict):
+ key_set |= set(d[k].keys())
+ return list(key_set)
+
+
+def tuple_paths(d: dict) -> Iterator[list[tuple]]:
+ def func(d, path):
+ if isinstance(d, dict):
+ if not d:
+ yield path
+ for k, v in d.items():
+ if isinstance(k, tuple) and key_name(k) != '__node_data':
+ for r in func(v, path + [k]):
+ yield r
+ else:
+ yield path
+ else:
+ yield path
+ for r in func(d, []):
+ yield r
-class NodeData(TypedDict):
- node_type: Optional[str]
- help_text: Optional[str]
- comp_help: Optional[dict[str, list]]
- command: Optional[str]
- path: Optional[list[str]]
+def match_tuple_paths(
+ path: list[str], paths: list[list[tuple[str, str]]]
+) -> list[list[tuple[str, str]]]:
+ return list(filter(lambda p: key_names(p) == path, paths))
-PathData: TypeAlias = dict[str, Union[NodeData|list['PathData']]]
+
+def get_node_data(d: dict) -> NodeData:
+ return d.get(('__node_data', None), {})
+
+
+def get_node_data_at_path(d: dict, tpath):
+ if not tpath:
+ return {}
+ # operates on actual paths, not names:
+ if not isinstance(tpath[0], tuple):
+ raise ValueError('must be path of tuples')
+ while tpath and d:
+ d = d.get(tpath[0], {})
+ tpath = tpath[1:]
+
+ return get_node_data(d)
+
+
+def node_data_difference(a: NodeData, b: NodeData):
+ out = ''
+ for fld in fields(NodeData):
+ if fld.name in ('children', 'file'):
+ continue
+ a_fld = getattr(a, fld.name)
+ b_fld = getattr(b, fld.name)
+ if a_fld != b_fld:
+ out += f'prev: {a.file} {a.path} {fld.name}: {a_fld}\n'
+ out += f'new: {b.file} {b.path} {fld.name}: {b_fld}\n'
+ out += '\n'
+
+ return out
+
+
+def collapse(d: OpData, acc: dict = None) -> tuple[dict, str, bool]:
+ err = False
+ inner_err = False
+ out = ''
+ inner_out = ''
+ if acc is None:
+ acc = {}
+ if not isinstance(d, dict):
+ return d
+ for k, v in d.items():
+ if isinstance(k, tuple):
+ name = key_name(k)
+ if name != '__node_data':
+ new_data = get_node_data(v)
+ if name in list(acc.keys()):
+ err = True
+ prev_data = acc[name].get('__node_data', {})
+ if prev_data:
+ out += f'prev: {prev_data["file"]} {prev_data["path"]}\n'
+ else:
+ out += '\n'
+ out += f'new: {new_data.file} {new_data.path}\n\n'
+ else:
+ acc[name] = {}
+ acc[name]['__node_data'] = asdict(new_data)
+ inner, o, e = collapse(v)
+ inner_err |= e
+ inner_out += o
+ acc[name].update(inner)
+ else:
+ name = k
+ acc[name] = v
+
+ err |= inner_err
+ out += inner_out
+
+ return acc, out, err
class OpXml:
def __init__(self):
self.op_ref = {}
- def define(self, op_ref: list[PathData]) -> None:
+ def define(self, op_ref: dict) -> None:
self.op_ref = op_ref
- def _get_op_ref_path(self, path: list[str]) -> list[PathData]:
- def _get_path_list(path: list[str], l: list[PathData]) -> list[PathData]:
- if not path:
- return l
- for d in l:
- if path[0] in list(d):
- return _get_path_list(path[1:], d[path[0]])
- return []
- l = self.op_ref
- return _get_path_list(path, l)
+ def walk(self, func):
+ def walk_op_data(obj, func):
+ if isinstance(obj, dict):
+ for k, v in obj.items():
+ if isinstance(k, tuple):
+ res = func(k, v)
+ yield res
+ yield from walk_op_data(v, func)
+
+ return walk_op_data(self.op_ref, func)
+
+ @staticmethod
+ def get_node_data_func(k, v):
+ if key_name(k) == '__node_data':
+ return v
+ return None
+
+ def walk_node_data(self):
+ return filterfalse(lambda x: x is None, self.walk(self.get_node_data_func))
+
+ def lookup(
+ self, path: list[str], tag_values: bool = False, last_node_type: str = ''
+ ) -> (OpData, list[str]):
+ path = path[:]
+
+ ref_path = []
+
+ def prune_tree(d: dict, p: list[str]):
+ p = p[:]
+ if not d or not isinstance(d, dict) or not p:
+ return d
+ op_data: dict = subdict_of_name(p[0], d)
+ op_keys = list(op_data.keys())
+ ref_path.append(p[0])
+ if len(p) < 2:
+ # check last node_type
+ if last_node_type:
+ keys = list(filter(lambda t: t[1] == last_node_type, op_keys))
+ values = list(map(lambda t: op_data[t], keys))
+ return dict(zip(keys, values))
+ return op_data
+
+ if p[1] not in key_names(next_keys(op_data)):
+ # check if tag_values
+ if tag_values:
+ p = p[2:]
+ keys = list(filter(is_tag_node, op_keys))
+ values = list(map(lambda t: prune_tree(op_data[t], p), keys))
+ return dict(zip(keys, values))
+ return {}
+
+ p = p[1:]
+ op_data = list(map(lambda t: prune_tree(op_data[t], p), op_keys))
+
+ return dict(zip(op_keys, op_data))
+
+ return prune_tree(self.op_ref, path), ref_path
+
+ def lookup_node_data(
+ self, path: list[str], tag_values: bool = False, last_node_type: str = ''
+ ) -> list[NodeData]:
+ res = []
+ d, ref_path = self.lookup(path, tag_values, last_node_type)
+ paths = list(tuple_paths(d))
+ paths = match_tuple_paths(ref_path, paths)
+ for p in paths:
+ res.append(get_node_data_at_path(d, p))
+
+ return res
diff --git a/python/vyos/xml_ref/update_cache.py b/python/vyos/xml_ref/update_cache.py
index 0842bcbe9..6643f9dc4 100755
--- a/python/vyos/xml_ref/update_cache.py
+++ b/python/vyos/xml_ref/update_cache.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2023 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# 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