From 775348a4cda34e6be16454d43c77b525e57c4e47 Mon Sep 17 00:00:00 2001 From: up-n-atom Date: Mon, 26 Feb 2024 14:16:40 -0500 Subject: vyos.ethtool: T6070: fix EEE reading the incorrect status line EEE enabled status is on the 2nd line of ethtool output and not the 3rd. Subsequently, reading the 3rd line was causing an out-of-bounds access for the bnx2x driver as well. --- python/vyos/ethtool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index ba638b280..f20fa452e 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -150,7 +150,7 @@ class Ethtool: self._eee = True # read current EEE setting, this returns: # EEE status: disabled || EEE status: enabled - inactive || EEE status: enabled - active - self._eee_enabled = bool('enabled' in out.splitlines()[2]) + self._eee_enabled = bool('enabled' in out.splitlines()[1]) def check_auto_negotiation_supported(self): """ Check if the NIC supports changing auto-negotiation """ -- cgit v1.2.3 From 3480d92a8c4d84e8c1f94a9362bac2be0cc77921 Mon Sep 17 00:00:00 2001 From: Nataliia Solomko Date: Wed, 28 Feb 2024 11:42:58 +0200 Subject: T5504 Keepalived VRRP ability to set more than one peer-address --- data/templates/high-availability/keepalived.conf.j2 | 6 +++++- interface-definitions/high-availability.xml.in | 1 + src/conf_mode/high-availability.py | 10 ++++++---- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/data/templates/high-availability/keepalived.conf.j2 b/data/templates/high-availability/keepalived.conf.j2 index d54f575b5..f34ce64e2 100644 --- a/data/templates/high-availability/keepalived.conf.j2 +++ b/data/templates/high-availability/keepalived.conf.j2 @@ -82,7 +82,11 @@ vrrp_instance {{ name }} { nopreempt {% endif %} {% if group_config.peer_address is vyos_defined %} - unicast_peer { {{ group_config.peer_address }} } + unicast_peer { +{% for peer_address in group_config.peer_address %} + {{ peer_address }} +{% endfor %} + } {% endif %} {% if group_config.hello_source_address is vyos_defined %} {% if group_config.peer_address is vyos_defined %} diff --git a/interface-definitions/high-availability.xml.in b/interface-definitions/high-availability.xml.in index 59f0f1052..aef57f8ae 100644 --- a/interface-definitions/high-availability.xml.in +++ b/interface-definitions/high-availability.xml.in @@ -195,6 +195,7 @@ + diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py index b3b27b14e..59d49ea67 100755 --- a/src/conf_mode/high-availability.py +++ b/src/conf_mode/high-availability.py @@ -125,8 +125,9 @@ def verify(ha): raise ConfigError(f'VRRP group "{group}" uses IPv4 but hello-source-address is IPv6!') if 'peer_address' in group_config: - if is_ipv6(group_config['peer_address']): - raise ConfigError(f'VRRP group "{group}" uses IPv4 but peer-address is IPv6!') + for peer_address in group_config['peer_address']: + if is_ipv6(peer_address): + raise ConfigError(f'VRRP group "{group}" uses IPv4 but peer-address is IPv6!') if vaddrs6: tmp = {'interface': interface, 'vrid': vrid, 'ipver': 'IPv6'} @@ -139,8 +140,9 @@ def verify(ha): raise ConfigError(f'VRRP group "{group}" uses IPv6 but hello-source-address is IPv4!') if 'peer_address' in group_config: - if is_ipv4(group_config['peer_address']): - raise ConfigError(f'VRRP group "{group}" uses IPv6 but peer-address is IPv4!') + for peer_address in group_config['peer_address']: + if is_ipv4(peer_address): + raise ConfigError(f'VRRP group "{group}" uses IPv6 but peer-address is IPv4!') # Check sync groups if 'vrrp' in ha and 'sync_group' in ha['vrrp']: for sync_group, sync_config in ha['vrrp']['sync_group'].items(): -- cgit v1.2.3 From 0ea3a454cf560171d3eb9d4d1b97b172c06360fe Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Wed, 28 Feb 2024 20:47:10 +0100 Subject: banner: T6077: implement ASCII contest winner default logo Implement VyOS ASCII art contest winners logo as the default for our MOTD --- data/templates/login/default_motd.j2 | 14 ++++++++++++++ src/conf_mode/system_login_banner.py | 22 +++++++++++----------- 2 files changed, 25 insertions(+), 11 deletions(-) create mode 100644 data/templates/login/default_motd.j2 diff --git a/data/templates/login/default_motd.j2 b/data/templates/login/default_motd.j2 new file mode 100644 index 000000000..8584d261a --- /dev/null +++ b/data/templates/login/default_motd.j2 @@ -0,0 +1,14 @@ +Welcome to VyOS! + + ┌── ┐ + . VyOS {{ version_data.version }} + └ ──┘ {{ version_data.release_train }} + + * Documentation: https://docs.vyos.io/en/{{ version_data.release_train | replace('current', 'latest') }} + * Project news: https://blog.vyos.io + * Bug reports: https://vyos.dev + +You can change this banner using "set system login banner post-login" command. + +VyOS is a free software distribution that includes multiple components, +you can check individual component licenses under /usr/share/doc/*/copyright diff --git a/src/conf_mode/system_login_banner.py b/src/conf_mode/system_login_banner.py index 65fa04417..923e1bf57 100755 --- a/src/conf_mode/system_login_banner.py +++ b/src/conf_mode/system_login_banner.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-2024 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -18,30 +18,26 @@ from sys import exit from copy import deepcopy from vyos.config import Config +from vyos.template import render from vyos.utils.file import write_file +from vyos.version import get_version_data from vyos import ConfigError from vyos import airbag airbag.enable() -try: - with open('/usr/share/vyos/default_motd') as f: - motd = f.read() -except: - # Use an empty banner if the default banner file cannot be read - motd = "\n" - PRELOGIN_FILE = r'/etc/issue' PRELOGIN_NET_FILE = r'/etc/issue.net' POSTLOGIN_FILE = r'/etc/motd' default_config_data = { 'issue': 'Welcome to VyOS - \\n \\l\n\n', - 'issue_net': '', - 'motd': motd + 'issue_net': '' } def get_config(config=None): banner = deepcopy(default_config_data) + banner['version_data'] = get_version_data() + if config: conf = config else: @@ -92,7 +88,11 @@ def generate(banner): def apply(banner): write_file(PRELOGIN_FILE, banner['issue']) write_file(PRELOGIN_NET_FILE, banner['issue_net']) - write_file(POSTLOGIN_FILE, banner['motd']) + if 'motd' in banner: + write_file(POSTLOGIN_FILE, banner['motd']) + else: + render(POSTLOGIN_FILE, 'login/default_motd.j2', banner, + permission=0o644, user='root', group='root') return None -- cgit v1.2.3 From 5c173c5935eab3a8bd0f169759617c4296a92df7 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Tue, 27 Feb 2024 14:54:35 -0600 Subject: configdep: T5839: remove trivially redundant config dependency calls --- python/vyos/configdep.py | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/python/vyos/configdep.py b/python/vyos/configdep.py index 64727d355..41252d8f5 100644 --- a/python/vyos/configdep.py +++ b/python/vyos/configdep.py @@ -1,4 +1,4 @@ -# Copyright 2023 VyOS maintainers and contributors +# Copyright 2023-2024 VyOS maintainers and contributors # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -35,6 +35,12 @@ dependency_dir = os.path.join(directories['data'], dependent_func: dict[str, list[typing.Callable]] = {} +DEBUG = False + +def debug_print(s: str): + if DEBUG: + print(s) + def canon_name(name: str) -> str: return os.path.splitext(name)[0].replace('-', '_') @@ -45,6 +51,26 @@ def canon_name_of_path(path: str) -> str: def caller_name() -> str: return stack()[2].filename +def name_of(f: typing.Callable) -> str: + return f.__name__ + +def names_of(l: list[typing.Callable]) -> list[str]: + return [name_of(f) for f in l] + +def remove_redundant(l: list[typing.Callable]) -> list[typing.Callable]: + names = set() + for e in reversed(l): + _ = l.remove(e) if name_of(e) in names else names.add(name_of(e)) + +def append_uniq(l: list[typing.Callable], e: typing.Callable): + """Append an element, removing earlier occurrences + + The list of dependencies is generally short and traversing the list on + each append is preferable to the cost of redundant script invocation. + """ + l.append(e) + remove_redundant(l) + def read_dependency_dict(dependency_dir: str = dependency_dir) -> dict: res = {} for dep_file in os.listdir(dependency_dir): @@ -95,16 +121,21 @@ def set_dependents(case: str, config: 'Config', tagnode: typing.Optional[str] = None): d = get_dependency_dict(config) k = canon_name_of_path(caller_name()) + tag_ext = f'_{tagnode}' if tagnode is not None else '' l = dependent_func.setdefault(k, []) for target in d[k][case]: func = def_closure(target, config, tagnode) - l.append(func) + func.__name__ = f'{target}{tag_ext}' + append_uniq(l, func) + debug_print(f'set_dependents: caller {k}, dependents {names_of(l)}') def call_dependents(): k = canon_name_of_path(caller_name()) l = dependent_func.get(k, []) + debug_print(f'call_dependents: caller {k}, dependents {names_of(l)}') while l: f = l.pop(0) + debug_print(f'calling: {f.__name__}') f() def called_as_dependent() -> bool: -- cgit v1.2.3 From 70e1df1b5fcb3b1791cca320ed45b71e01e1ffda Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Tue, 27 Feb 2024 14:54:50 -0600 Subject: configdep: T5660: remove global redundancies under vyos-configd --- python/vyos/configdep.py | 18 ++++++++++++++---- src/services/vyos-configd | 19 ++++++++++++++++--- src/shim/vyshim.c | 12 ++++++++++-- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/python/vyos/configdep.py b/python/vyos/configdep.py index 41252d8f5..73bd9ea96 100644 --- a/python/vyos/configdep.py +++ b/python/vyos/configdep.py @@ -33,9 +33,10 @@ if typing.TYPE_CHECKING: dependency_dir = os.path.join(directories['data'], 'config-mode-dependencies') -dependent_func: dict[str, list[typing.Callable]] = {} +local_dependent_func: dict[str, list[typing.Callable]] = {} DEBUG = False +FORCE_LOCAL = False def debug_print(s: str): if DEBUG: @@ -122,16 +123,25 @@ def set_dependents(case: str, config: 'Config', d = get_dependency_dict(config) k = canon_name_of_path(caller_name()) tag_ext = f'_{tagnode}' if tagnode is not None else '' - l = dependent_func.setdefault(k, []) + if hasattr(config, 'dependent_func') and not FORCE_LOCAL: + dependent_func = getattr(config, 'dependent_func') + l = dependent_func.setdefault('vyos_configd', []) + else: + dependent_func = local_dependent_func + l = dependent_func.setdefault(k, []) for target in d[k][case]: func = def_closure(target, config, tagnode) func.__name__ = f'{target}{tag_ext}' append_uniq(l, func) debug_print(f'set_dependents: caller {k}, dependents {names_of(l)}') -def call_dependents(): +def call_dependents(dependent_func: dict = None): k = canon_name_of_path(caller_name()) - l = dependent_func.get(k, []) + if dependent_func is None or FORCE_LOCAL: + dependent_func = local_dependent_func + l = dependent_func.get(k, []) + else: + l = dependent_func.get('vyos_configd', []) debug_print(f'call_dependents: caller {k}, dependents {names_of(l)}') while l: f = l.pop(0) diff --git a/src/services/vyos-configd b/src/services/vyos-configd index 355182b26..648a017d5 100755 --- a/src/services/vyos-configd +++ b/src/services/vyos-configd @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2023 VyOS maintainers and contributors +# Copyright (C) 2020-2024 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -19,6 +19,7 @@ import sys import grp import re import json +import typing import logging import signal import importlib.util @@ -29,6 +30,7 @@ from vyos.defaults import directories from vyos.utils.boot import boot_configuration_complete from vyos.configsource import ConfigSourceString from vyos.configsource import ConfigSourceError +from vyos.configdep import call_dependents from vyos.config import Config from vyos import ConfigError @@ -198,10 +200,12 @@ def initialization(socket): return None config = Config(config_source=configsource) + dependent_func: dict[str, list[typing.Callable]] = {} + setattr(config, 'dependent_func', dependent_func) return config -def process_node_data(config, data) -> int: +def process_node_data(config, data, last: bool = False) -> int: if not config: logger.critical(f"Empty config") return R_ERROR_DAEMON @@ -223,11 +227,18 @@ def process_node_data(config, data) -> int: args.insert(0, f'{script_name}.py') if script_name not in include_set: + # call dependents now if last element of prio queue is run + # independent of configd + if last: + call_dependents(dependent_func=config.dependent_func) return R_PASS with stdout_redirected(session_out, session_mode): result = run_script(conf_mode_scripts[script_name], config, args) + if last: + call_dependents(dependent_func=config.dependent_func) + return result def remove_if_file(f: str): @@ -281,7 +292,9 @@ if __name__ == '__main__': socket.send(resp.encode()) config = initialization(socket) elif message["type"] == "node": - res = process_node_data(config, message["data"]) + if message["last"]: + logger.debug(f'final element of priority queue') + res = process_node_data(config, message["data"], message["last"]) response = res.to_bytes(1, byteorder=sys.byteorder) logger.debug(f"Sending response {res}") socket.send(response) diff --git a/src/shim/vyshim.c b/src/shim/vyshim.c index cae8b6152..41723e7a4 100644 --- a/src/shim/vyshim.c +++ b/src/shim/vyshim.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 VyOS maintainers and contributors + * Copyright (C) 2020-2024 VyOS maintainers and contributors * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 or later as @@ -49,6 +49,7 @@ #define GET_SESSION "cli-shell-api --show-working-only --show-show-defaults --show-ignore-edit showConfig" #define COMMIT_MARKER "/var/tmp/initial_in_commit" +#define QUEUE_MARKER "/var/tmp/last_in_queue" enum { SUCCESS = 1 << 0, @@ -77,6 +78,7 @@ int main(int argc, char* argv[]) int ex_index; int init_timeout = 0; + int last = 0; debug_print("Connecting to vyos-configd ...\n"); zmq_connect(requester, SOCKET_PATH); @@ -101,10 +103,16 @@ int main(int argc, char* argv[]) return ret; } + if (access(QUEUE_MARKER, F_OK) != -1) { + last = 1; + remove(QUEUE_MARKER); + } + char error_code[1]; debug_print("Sending node data ...\n"); - char *string_node_data_msg = mkjson(MKJSON_OBJ, 2, + char *string_node_data_msg = mkjson(MKJSON_OBJ, 3, MKJSON_STRING, "type", "node", + MKJSON_BOOL, "last", last, MKJSON_STRING, "data", &string_node_data[0]); zmq_send(requester, string_node_data_msg, strlen(string_node_data_msg), 0); -- cgit v1.2.3 From 36883ebf0f820003ec86e14e7612ce113630def2 Mon Sep 17 00:00:00 2001 From: Nataliia Solomko Date: Thu, 29 Feb 2024 10:30:41 +0200 Subject: T5504: Added smoketest for multiple peer addresses --- .../scripts/cli/test_high-availability_vrrp.py | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/smoketest/scripts/cli/test_high-availability_vrrp.py b/smoketest/scripts/cli/test_high-availability_vrrp.py index 98259d830..1bb35e422 100755 --- a/smoketest/scripts/cli/test_high-availability_vrrp.py +++ b/smoketest/scripts/cli/test_high-availability_vrrp.py @@ -237,5 +237,32 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase): self.assertIn(f'track_interface', config) self.assertIn(f' {none_vrrp_interface}', config) + def test_05_set_multiple_peer_address(self): + group = 'VyOS-WAN' + vlan_id = '24' + vip = '100.64.24.1/24' + peer_address_1 = '192.0.2.1' + peer_address_2 = '192.0.2.2' + vrid = '150' + group_base = base_path + ['vrrp', 'group', group] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', '100.64.24.11/24']) + self.cli_set(group_base + ['interface', vrrp_interface]) + self.cli_set(group_base + ['address', vip]) + self.cli_set(group_base + ['peer-address', peer_address_1]) + self.cli_set(group_base + ['peer-address', peer_address_2]) + self.cli_set(group_base + ['vrid', vrid]) + + # commit changes + self.cli_commit() + + config = getConfig(f'vrrp_instance {group}') + + self.assertIn(f'interface {vrrp_interface}', config) + self.assertIn(f'virtual_router_id {vrid}', config) + self.assertIn(f'unicast_peer', config) + self.assertIn(f' {peer_address_1}', config) + self.assertIn(f' {peer_address_2}', config) + if __name__ == '__main__': unittest.main(verbosity=2) -- cgit v1.2.3 From 3712f28025a5bc99e941b5212091a2732b9f6d6c Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Thu, 29 Feb 2024 22:08:29 +0100 Subject: vyos-hostsd: T4270: resolve only hostname without domain name to 127.0.1.1 This is a fix for commit 665ae50729 ("vyos-hostsd: T4270: do not resolve local router FQDN to 127.0.1.1") as it made calls to sudo super slow due to: sudo: unable to resolve host vyos: System error To avoid the initial issue we only add the hostname without domain name, thus the FQDN is not resolved by powerdns. --- data/templates/vyos-hostsd/hosts.j2 | 1 + 1 file changed, 1 insertion(+) diff --git a/data/templates/vyos-hostsd/hosts.j2 b/data/templates/vyos-hostsd/hosts.j2 index 71fa335da..62ecf3ad0 100644 --- a/data/templates/vyos-hostsd/hosts.j2 +++ b/data/templates/vyos-hostsd/hosts.j2 @@ -4,6 +4,7 @@ # Local host 127.0.0.1 localhost +127.0.1.1 {{ host_name }} # The following lines are desirable for IPv6 capable hosts ::1 localhost ip6-localhost ip6-loopback -- cgit v1.2.3