diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/conf_mode/container.py | 23 | ||||
-rwxr-xr-x | src/conf_mode/firewall.py | 9 | ||||
-rwxr-xr-x | src/conf_mode/high-availability.py | 10 | ||||
-rwxr-xr-x | src/conf_mode/nat.py | 18 | ||||
-rwxr-xr-x | src/conf_mode/protocols_ospf.py | 6 | ||||
-rwxr-xr-x | src/conf_mode/protocols_ospfv3.py | 8 | ||||
-rwxr-xr-x | src/conf_mode/system_conntrack.py | 4 | ||||
-rwxr-xr-x | src/conf_mode/system_login_banner.py | 22 | ||||
-rwxr-xr-x | src/conf_mode/system_option.py | 2 | ||||
-rwxr-xr-x | src/conf_mode/vpn_ipsec.py | 21 | ||||
-rwxr-xr-x | src/conf_mode/vrf.py | 18 | ||||
-rwxr-xr-x | src/init/vyos-router | 4 | ||||
-rwxr-xr-x | src/migration-scripts/dhcp-server/6-to-7 | 69 | ||||
-rwxr-xr-x | src/migration-scripts/dhcp-server/7-to-8 | 66 | ||||
-rwxr-xr-x | src/migration-scripts/dhcp-server/8-to-9 | 42 | ||||
-rwxr-xr-x | src/migration-scripts/dhcp-server/9-to-10 | 75 | ||||
-rwxr-xr-x | src/op_mode/container.py | 42 | ||||
-rwxr-xr-x | src/services/vyos-configd | 19 | ||||
-rwxr-xr-x | src/services/vyos-http-api-server | 3 | ||||
-rw-r--r-- | src/shim/vyshim.c | 12 | ||||
-rwxr-xr-x | src/system/vyos-event-handler.py | 7 |
21 files changed, 328 insertions, 152 deletions
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index 321d00abf..e967bee71 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2023 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -32,7 +32,6 @@ from vyos.utils.file import write_file from vyos.utils.process import call from vyos.utils.process import cmd from vyos.utils.process import run -from vyos.utils.process import rc_cmd from vyos.template import bracketize_ipv6 from vyos.template import inc_ip from vyos.template import is_ipv4 @@ -251,7 +250,7 @@ def verify(container): if 'authentication' not in registry_config: continue if not {'username', 'password'} <= set(registry_config['authentication']): - raise ConfigError('If registry username or or password is defined, so must be the other!') + raise ConfigError('Container registry requires both username and password to be set!') return None @@ -401,24 +400,6 @@ def generate(container): write_file(f'/etc/containers/networks/{network}.json', json_write(tmp, indent=2)) - if 'registry' in container: - cmd = f'podman logout --all' - rc, out = rc_cmd(cmd) - if rc != 0: - raise ConfigError(out) - - for registry, registry_config in container['registry'].items(): - if 'disable' in registry_config: - continue - if 'authentication' in registry_config: - if {'username', 'password'} <= set(registry_config['authentication']): - username = registry_config['authentication']['username'] - password = registry_config['authentication']['password'] - cmd = f'podman login --username {username} --password {password} {registry}' - rc, out = rc_cmd(cmd) - if rc != 0: - raise ConfigError(out) - render(config_containers, 'container/containers.conf.j2', container) render(config_registry, 'container/registries.conf.j2', container) render(config_storage, 'container/storage.conf.j2', container) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index acb7dfa41..3c27655b0 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -282,6 +282,15 @@ def verify_rule(firewall, rule_conf, ipv6): if direction in rule_conf: if 'name' in rule_conf[direction] and 'group' in rule_conf[direction]: raise ConfigError(f'Cannot specify both interface group and interface name for {direction}') + if 'group' in rule_conf[direction]: + group_name = rule_conf[direction]['group'] + if group_name[0] == '!': + group_name = group_name[1:] + group_obj = dict_search_args(firewall, 'group', 'interface_group', group_name) + if group_obj is None: + raise ConfigError(f'Invalid interface group "{group_name}" on firewall rule') + if not group_obj: + Warning(f'interface-group "{group_name}" has no members!') def verify_nested_group(group_name, group, groups, seen): if 'include' not in group: 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(): diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 26822b755..b3f38c04a 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -153,6 +153,15 @@ def verify(nat): elif 'name' in config['outbound_interface']: if config['outbound_interface']['name'] not in 'any' and config['outbound_interface']['name'] not in interfaces(): Warning(f'NAT interface "{config["outbound_interface"]["name"]}" for source NAT rule "{rule}" does not exist!') + else: + group_name = config['outbound_interface']['group'] + if group_name[0] == '!': + group_name = group_name[1:] + group_obj = dict_search_args(nat['firewall_group'], 'interface_group', group_name) + if group_obj is None: + raise ConfigError(f'Invalid interface group "{group_name}" on source nat rule') + if not group_obj: + Warning(f'interface-group "{group_name}" has no members!') if not dict_search('translation.address', config) and not dict_search('translation.port', config): if 'exclude' not in config and 'backend' not in config['load_balance']: @@ -177,6 +186,15 @@ def verify(nat): elif 'name' in config['inbound_interface']: if config['inbound_interface']['name'] not in 'any' and config['inbound_interface']['name'] not in interfaces(): Warning(f'NAT interface "{config["inbound_interface"]["name"]}" for destination NAT rule "{rule}" does not exist!') + else: + group_name = config['inbound_interface']['group'] + if group_name[0] == '!': + group_name = group_name[1:] + group_obj = dict_search_args(nat['firewall_group'], 'interface_group', group_name) + if group_obj is None: + raise ConfigError(f'Invalid interface group "{group_name}" on destination nat rule') + if not group_obj: + Warning(f'interface-group "{group_name}" has no members!') if not dict_search('translation.address', config) and not dict_search('translation.port', config) and 'redirect' not in config['translation']: if 'exclude' not in config and 'backend' not in config['load_balance']: diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index 34cf49286..695842795 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2023 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -91,6 +91,8 @@ def get_config(config=None): for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']: if dict_search(f'redistribute.{protocol}', ospf) is None: del default_values['redistribute'][protocol] + if not bool(default_values['redistribute']): + del default_values['redistribute'] for interface in ospf.get('interface', []): # We need to reload the defaults on every pass b/c of @@ -213,7 +215,7 @@ def verify(ospf): raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\ f'and no-php-flag configured at the same time.') - # Check for index ranges being larger than the segment routing global block + # Check for index ranges being larger than the segment routing global block if dict_search('segment_routing.global_block', ospf): g_high_label_value = dict_search('segment_routing.global_block.high_label_value', ospf) g_low_label_value = dict_search('segment_routing.global_block.low_label_value', ospf) diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py index 5b1adce30..afd767dbf 100755 --- a/src/conf_mode/protocols_ospfv3.py +++ b/src/conf_mode/protocols_ospfv3.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2023 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -85,6 +85,12 @@ def get_config(config=None): if 'graceful_restart' not in ospfv3: del default_values['graceful_restart'] + for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'ripng', 'static']: + if dict_search(f'redistribute.{protocol}', ospfv3) is None: + del default_values['redistribute'][protocol] + if not bool(default_values['redistribute']): + del default_values['redistribute'] + default_values.pop('interface', {}) # merge in remaining default values diff --git a/src/conf_mode/system_conntrack.py b/src/conf_mode/system_conntrack.py index 7f6c71440..e075bc928 100755 --- a/src/conf_mode/system_conntrack.py +++ b/src/conf_mode/system_conntrack.py @@ -104,6 +104,10 @@ def get_config(config=None): if conf.exists(['service', 'conntrack-sync']): set_dependents('conntrack_sync', conf) + # If conntrack status changes, VRF zone rules need updating + if conf.exists(['vrf']): + set_dependents('vrf', conf) + return conntrack def verify(conntrack): 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 diff --git a/src/conf_mode/system_option.py b/src/conf_mode/system_option.py index 3b5b67437..7ed451e16 100755 --- a/src/conf_mode/system_option.py +++ b/src/conf_mode/system_option.py @@ -92,6 +92,8 @@ def generate(options): if 'kernel' in options: if 'disable_mitigations' in options['kernel']: cmdline_options.append('mitigations=off') + if 'disable_power_saving' in options['kernel']: + cmdline_options.append('intel_idle.max_cstate=0 processor.max_cstate=1') grub_util.update_kernel_cmdline_options(' '.join(cmdline_options)) return None diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index d074ed159..388f2a709 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -25,6 +25,8 @@ from time import time from vyos.base import Warning from vyos.config import Config +from vyos.configdep import set_dependents +from vyos.configdep import call_dependents from vyos.configdict import leaf_node_changed from vyos.configverify import verify_interface_exists from vyos.configverify import dynamic_interface_pattern @@ -97,6 +99,9 @@ def get_config(config=None): ipsec['interface_change'] = leaf_node_changed(conf, base + ['interface']) ipsec['nhrp_exists'] = conf.exists(['protocols', 'nhrp', 'tunnel']) + if ipsec['nhrp_exists']: + set_dependents('nhrp', conf) + tmp = conf.get_config_dict(l2tp_base, key_mangling=('-', '_'), no_tag_node_value_mangle=True, get_first_key=True) @@ -575,13 +580,6 @@ def generate(ipsec): render(interface_conf, 'ipsec/interfaces_use.conf.j2', ipsec) render(swanctl_conf, 'ipsec/swanctl.conf.j2', ipsec) -def resync_nhrp(ipsec): - if ipsec and not ipsec['nhrp_exists']: - return - - tmp = run('/usr/libexec/vyos/conf_mode/protocols_nhrp.py') - if tmp > 0: - print('ERROR: failed to reapply NHRP settings!') def apply(ipsec): systemd_service = 'strongswan.service' @@ -590,7 +588,14 @@ def apply(ipsec): else: call(f'systemctl reload-or-restart {systemd_service}') - resync_nhrp(ipsec) + if ipsec.get('nhrp_exists', False): + try: + call_dependents() + except ConfigError: + # Ignore config errors on dependent due to being called too early. Example: + # ConfigError("ConfigError('Interface ethN requires an IP address!')") + pass + if __name__ == '__main__': try: diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index a2f4956be..16908100f 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -23,6 +23,7 @@ from vyos.config import Config from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.configverify import verify_route_map +from vyos.firewall import conntrack_required from vyos.ifconfig import Interface from vyos.template import render from vyos.template import render_to_string @@ -41,6 +42,12 @@ airbag.enable() config_file = '/etc/iproute2/rt_tables.d/vyos-vrf.conf' k_mod = ['vrf'] +nftables_table = 'inet vrf_zones' +nftables_rules = { + 'vrf_zones_ct_in': 'counter ct original zone set iifname map @ct_iface_map', + 'vrf_zones_ct_out': 'counter ct original zone set oifname map @ct_iface_map' +} + def has_rule(af : str, priority : int, table : str=None): """ Check if a given ip rule exists @@ -114,6 +121,9 @@ def get_config(config=None): routes = vrf_routing(conf, name) if routes: vrf['vrf_remove'][name]['route'] = routes + if 'name' in vrf: + vrf['conntrack'] = conntrack_required(conf) + # We also need the route-map information from the config # # XXX: one MUST always call this without the key_mangling() option! See @@ -294,6 +304,14 @@ def apply(vrf): nft_add_element = f'add element inet vrf_zones ct_iface_map {{ "{name}" : {table} }}' cmd(f'nft {nft_add_element}') + if vrf['conntrack']: + for chain, rule in nftables_rules.items(): + cmd(f'nft add rule inet vrf_zones {chain} {rule}') + + if 'name' not in vrf or not vrf['conntrack']: + for chain, rule in nftables_rules.items(): + cmd(f'nft flush chain inet vrf_zones {chain}') + # Apply FRR filters zebra_daemon = 'zebra' # Save original configuration prior to starting any commit actions diff --git a/src/init/vyos-router b/src/init/vyos-router index 74e3ca51b..adf892371 100755 --- a/src/init/vyos-router +++ b/src/init/vyos-router @@ -281,8 +281,8 @@ cleanup_post_commit_hooks () { # note that this approach only supports hooks that are "configured", # i.e., it does not support hooks that need to always be present. cpostdir=$(cli-shell-api getPostCommitHookDir) - # exclude commits hooks from vyatta-cfg - excluded="10vyatta-log-commit.pl 99vyos-user-postcommit-hooks" + # exclude commit hooks that need to always be present + excluded="00vyos-sync 10vyatta-log-commit.pl 99vyos-user-postcommit-hooks" if [ -d "$cpostdir" ]; then for f in $cpostdir/*; do if [[ ! $excluded =~ $(basename $f) ]]; then diff --git a/src/migration-scripts/dhcp-server/6-to-7 b/src/migration-scripts/dhcp-server/6-to-7 index ccf385a30..e6c298a60 100755 --- a/src/migration-scripts/dhcp-server/6-to-7 +++ b/src/migration-scripts/dhcp-server/6-to-7 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2023 VyOS maintainers and contributors +# Copyright (C) 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 @@ -14,19 +14,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# T3316: Migrate to Kea -# - global-parameters will not function -# - shared-network-parameters will not function -# - subnet-parameters will not function -# - static-mapping-parameters will not function -# - host-decl-name is on by default, option removed -# - ping-check no longer supported -# - failover is default enabled on all subnets that exist on failover servers +# T6079: Disable duplicate static mappings import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 2): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) @@ -38,46 +31,42 @@ with open(file_name, 'r') as f: base = ['service', 'dhcp-server'] config = ConfigTree(config_file) -if not config.exists(base): +if not config.exists(base + ['shared-network-name']): # Nothing to do - sys.exit(0) + exit(0) -if config.exists(base + ['host-decl-name']): - config.delete(base + ['host-decl-name']) +# Run this for every instance if 'shared-network-name' +for network in config.list_nodes(base + ['shared-network-name']): + base_network = base + ['shared-network-name', network] -if config.exists(base + ['global-parameters']): - config.delete(base + ['global-parameters']) + if not config.exists(base_network + ['subnet']): + continue -if config.exists(base + ['shared-network-name']): - for network in config.list_nodes(base + ['shared-network-name']): - base_network = base + ['shared-network-name', network] + for subnet in config.list_nodes(base_network + ['subnet']): + base_subnet = base_network + ['subnet', subnet] - if config.exists(base_network + ['ping-check']): - config.delete(base_network + ['ping-check']) + if config.exists(base_subnet + ['static-mapping']): + used_mac = [] + used_ip = [] - if config.exists(base_network + ['shared-network-parameters']): - config.delete(base_network +['shared-network-parameters']) + for mapping in config.list_nodes(base_subnet + ['static-mapping']): + base_mapping = base_subnet + ['static-mapping', mapping] - if not config.exists(base_network + ['subnet']): - continue + if config.exists(base_mapping + ['mac-address']): + mac = config.return_value(base_mapping + ['mac-address']) - # Run this for every specified 'subnet' - for subnet in config.list_nodes(base_network + ['subnet']): - base_subnet = base_network + ['subnet', subnet] + if mac in used_mac: + config.set(base_mapping + ['disable']) + else: + used_mac.append(mac) - if config.exists(base_subnet + ['enable-failover']): - config.delete(base_subnet + ['enable-failover']) + if config.exists(base_mapping + ['ip-address']): + ip = config.return_value(base_mapping + ['ip-address']) - if config.exists(base_subnet + ['ping-check']): - config.delete(base_subnet + ['ping-check']) - - if config.exists(base_subnet + ['subnet-parameters']): - config.delete(base_subnet + ['subnet-parameters']) - - if config.exists(base_subnet + ['static-mapping']): - for mapping in config.list_nodes(base_subnet + ['static-mapping']): - if config.exists(base_subnet + ['static-mapping', mapping, 'static-mapping-parameters']): - config.delete(base_subnet + ['static-mapping', mapping, 'static-mapping-parameters']) + if ip in used_ip: + config.set(base_subnet + ['static-mapping', mapping, 'disable']) + else: + used_ip.append(ip) try: with open(file_name, 'w') as f: diff --git a/src/migration-scripts/dhcp-server/7-to-8 b/src/migration-scripts/dhcp-server/7-to-8 index 151aa6d7b..ccf385a30 100755 --- a/src/migration-scripts/dhcp-server/7-to-8 +++ b/src/migration-scripts/dhcp-server/7-to-8 @@ -14,16 +14,19 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# T3316: -# - Adjust hostname to have valid FQDN characters only (underscores aren't allowed anymore) -# - Rename "service dhcp-server shared-network-name ... static-mapping <hostname> mac-address ..." -# to "service dhcp-server shared-network-name ... static-mapping <hostname> mac ..." +# T3316: Migrate to Kea +# - global-parameters will not function +# - shared-network-parameters will not function +# - subnet-parameters will not function +# - static-mapping-parameters will not function +# - host-decl-name is on by default, option removed +# - ping-check no longer supported +# - failover is default enabled on all subnets that exist on failover servers import sys -import re from vyos.configtree import ConfigTree -if len(sys.argv) < 2: +if (len(sys.argv) < 2): print("Must specify file name!") sys.exit(1) @@ -32,30 +35,49 @@ file_name = sys.argv[1] with open(file_name, 'r') as f: config_file = f.read() -base = ['service', 'dhcp-server', 'shared-network-name'] +base = ['service', 'dhcp-server'] config = ConfigTree(config_file) if not config.exists(base): # Nothing to do sys.exit(0) -for network in config.list_nodes(base): - # Run this for every specified 'subnet' - if config.exists(base + [network, 'subnet']): - for subnet in config.list_nodes(base + [network, 'subnet']): - base_subnet = base + [network, 'subnet', subnet] - if config.exists(base_subnet + ['static-mapping']): - for hostname in config.list_nodes(base_subnet + ['static-mapping']): - base_mapping = base_subnet + ['static-mapping', hostname] +if config.exists(base + ['host-decl-name']): + config.delete(base + ['host-decl-name']) + +if config.exists(base + ['global-parameters']): + config.delete(base + ['global-parameters']) + +if config.exists(base + ['shared-network-name']): + for network in config.list_nodes(base + ['shared-network-name']): + base_network = base + ['shared-network-name', network] + + if config.exists(base_network + ['ping-check']): + config.delete(base_network + ['ping-check']) + + if config.exists(base_network + ['shared-network-parameters']): + config.delete(base_network +['shared-network-parameters']) - # Rename the 'mac-address' node to 'mac' - if config.exists(base_mapping + ['mac-address']): - config.rename(base_mapping + ['mac-address'], 'mac') + if not config.exists(base_network + ['subnet']): + continue - # Adjust hostname to have valid FQDN characters only - new_hostname = re.sub(r'[^a-zA-Z0-9-.]', '-', hostname) - if new_hostname != hostname: - config.rename(base_mapping, new_hostname) + # Run this for every specified 'subnet' + for subnet in config.list_nodes(base_network + ['subnet']): + base_subnet = base_network + ['subnet', subnet] + + if config.exists(base_subnet + ['enable-failover']): + config.delete(base_subnet + ['enable-failover']) + + if config.exists(base_subnet + ['ping-check']): + config.delete(base_subnet + ['ping-check']) + + if config.exists(base_subnet + ['subnet-parameters']): + config.delete(base_subnet + ['subnet-parameters']) + + if config.exists(base_subnet + ['static-mapping']): + for mapping in config.list_nodes(base_subnet + ['static-mapping']): + if config.exists(base_subnet + ['static-mapping', mapping, 'static-mapping-parameters']): + config.delete(base_subnet + ['static-mapping', mapping, 'static-mapping-parameters']) try: with open(file_name, 'w') as f: diff --git a/src/migration-scripts/dhcp-server/8-to-9 b/src/migration-scripts/dhcp-server/8-to-9 index 810e403a6..151aa6d7b 100755 --- a/src/migration-scripts/dhcp-server/8-to-9 +++ b/src/migration-scripts/dhcp-server/8-to-9 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2024 VyOS maintainers and contributors +# Copyright (C) 2023 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 @@ -15,8 +15,9 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # T3316: -# - Migrate dhcp options under new option node -# - Add subnet IDs to existing subnets +# - Adjust hostname to have valid FQDN characters only (underscores aren't allowed anymore) +# - Rename "service dhcp-server shared-network-name ... static-mapping <hostname> mac-address ..." +# to "service dhcp-server shared-network-name ... static-mapping <hostname> mac ..." import sys import re @@ -38,34 +39,23 @@ if not config.exists(base): # Nothing to do sys.exit(0) -option_nodes = ['bootfile-name', 'bootfile-server', 'bootfile-size', 'captive-portal', - 'client-prefix-length', 'default-router', 'domain-name', 'domain-search', - 'name-server', 'ip-forwarding', 'ipv6-only-preferred', 'ntp-server', - 'pop-server', 'server-identifier', 'smtp-server', 'static-route', - 'tftp-server-name', 'time-offset', 'time-server', 'time-zone', - 'vendor-option', 'wins-server', 'wpad-url'] - -subnet_id = 1 - for network in config.list_nodes(base): - for option in option_nodes: - if config.exists(base + [network, option]): - config.set(base + [network, 'option']) - config.copy(base + [network, option], base + [network, 'option', option]) - config.delete(base + [network, option]) - + # Run this for every specified 'subnet' if config.exists(base + [network, 'subnet']): for subnet in config.list_nodes(base + [network, 'subnet']): base_subnet = base + [network, 'subnet', subnet] - - for option in option_nodes: - if config.exists(base_subnet + [option]): - config.set(base_subnet + ['option']) - config.copy(base_subnet + [option], base_subnet + ['option', option]) - config.delete(base_subnet + [option]) + if config.exists(base_subnet + ['static-mapping']): + for hostname in config.list_nodes(base_subnet + ['static-mapping']): + base_mapping = base_subnet + ['static-mapping', hostname] + + # Rename the 'mac-address' node to 'mac' + if config.exists(base_mapping + ['mac-address']): + config.rename(base_mapping + ['mac-address'], 'mac') - config.set(base_subnet + ['subnet-id'], value=subnet_id) - subnet_id += 1 + # Adjust hostname to have valid FQDN characters only + new_hostname = re.sub(r'[^a-zA-Z0-9-.]', '-', hostname) + if new_hostname != hostname: + config.rename(base_mapping, new_hostname) try: with open(file_name, 'w') as f: diff --git a/src/migration-scripts/dhcp-server/9-to-10 b/src/migration-scripts/dhcp-server/9-to-10 new file mode 100755 index 000000000..810e403a6 --- /dev/null +++ b/src/migration-scripts/dhcp-server/9-to-10 @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# T3316: +# - Migrate dhcp options under new option node +# - Add subnet IDs to existing subnets + +import sys +import re +from vyos.configtree import ConfigTree + +if len(sys.argv) < 2: + print("Must specify file name!") + sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +base = ['service', 'dhcp-server', 'shared-network-name'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + sys.exit(0) + +option_nodes = ['bootfile-name', 'bootfile-server', 'bootfile-size', 'captive-portal', + 'client-prefix-length', 'default-router', 'domain-name', 'domain-search', + 'name-server', 'ip-forwarding', 'ipv6-only-preferred', 'ntp-server', + 'pop-server', 'server-identifier', 'smtp-server', 'static-route', + 'tftp-server-name', 'time-offset', 'time-server', 'time-zone', + 'vendor-option', 'wins-server', 'wpad-url'] + +subnet_id = 1 + +for network in config.list_nodes(base): + for option in option_nodes: + if config.exists(base + [network, option]): + config.set(base + [network, 'option']) + config.copy(base + [network, option], base + [network, 'option', option]) + config.delete(base + [network, option]) + + if config.exists(base + [network, 'subnet']): + for subnet in config.list_nodes(base + [network, 'subnet']): + base_subnet = base + [network, 'subnet', subnet] + + for option in option_nodes: + if config.exists(base_subnet + [option]): + config.set(base_subnet + ['option']) + config.copy(base_subnet + [option], base_subnet + ['option', option]) + config.delete(base_subnet + [option]) + + config.set(base_subnet + ['subnet-id'], value=subnet_id) + subnet_id += 1 + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) diff --git a/src/op_mode/container.py b/src/op_mode/container.py index 5a022d0c0..d29af8821 100755 --- a/src/op_mode/container.py +++ b/src/op_mode/container.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-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 @@ -20,6 +20,8 @@ import sys from sys import exit from vyos.utils.process import cmd +from vyos.utils.process import call +from vyos.utils.process import rc_cmd import vyos.opmode @@ -29,23 +31,51 @@ def _get_json_data(command: str) -> list: """ return cmd(f'{command} --format json') - def _get_raw_data(command: str) -> list: json_data = _get_json_data(command) data = json.loads(json_data) return data def add_image(name: str): - from vyos.utils.process import rc_cmd + """ Pull image from container registry. If registry authentication + is defined within VyOS CLI, credentials are used to login befroe pull """ + from vyos.configquery import ConfigTreeQuery + + conf = ConfigTreeQuery() + container = conf.get_config_dict(['container', 'registry']) + + do_logout = False + if 'registry' in container: + for registry, registry_config in container['registry'].items(): + if 'disable' in registry_config: + continue + if 'authentication' in registry_config: + do_logout = True + if {'username', 'password'} <= set(registry_config['authentication']): + username = registry_config['authentication']['username'] + password = registry_config['authentication']['password'] + cmd = f'podman login --username {username} --password {password} {registry}' + rc, out = rc_cmd(cmd) + if rc != 0: raise vyos.opmode.InternalError(out) rc, output = rc_cmd(f'podman image pull {name}') if rc != 0: raise vyos.opmode.InternalError(output) + if do_logout: + rc_cmd('podman logout --all') + def delete_image(name: str): from vyos.utils.process import rc_cmd - rc, output = rc_cmd(f'podman image rm --force {name}') + if name == 'all': + # gather list of all images and pass them to the removal list + name = cmd('sudo podman image ls --quiet') + # If there are no container images left, we can not delete them all + if not name: return + # replace newline with whitespace + name = name.replace('\n', ' ') + rc, output = rc_cmd(f'podman image rm {name}') if rc != 0: raise vyos.opmode.InternalError(output) @@ -57,7 +87,6 @@ def show_container(raw: bool): else: return cmd(command) - def show_image(raw: bool): command = 'podman image ls' container_data = _get_raw_data('podman image ls') @@ -66,7 +95,6 @@ def show_image(raw: bool): else: return cmd(command) - def show_network(raw: bool): command = 'podman network ls' container_data = _get_raw_data(command) @@ -75,7 +103,6 @@ def show_network(raw: bool): else: return cmd(command) - def restart(name: str): from vyos.utils.process import rc_cmd @@ -86,7 +113,6 @@ def restart(name: str): print(f'Container "{name}" restarted!') return output - if __name__ == '__main__': try: res = vyos.opmode.run(sys.modules[__name__]) 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/services/vyos-http-api-server b/src/services/vyos-http-api-server index 40d442e30..a7b14a1a3 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -459,7 +459,6 @@ def _configure_op(data: Union[ConfigureModel, ConfigureListModel, request: Request, background_tasks: BackgroundTasks): session = app.state.vyos_session env = session.get_session_env() - config = Config(session_env=env) endpoint = request.url.path @@ -474,6 +473,8 @@ def _configure_op(data: Union[ConfigureModel, ConfigureListModel, # so the lock is really global lock.acquire() + config = Config(session_env=env) + status = 200 msg = None error_msg = None 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); diff --git a/src/system/vyos-event-handler.py b/src/system/vyos-event-handler.py index 74112ec91..dd2793046 100755 --- a/src/system/vyos-event-handler.py +++ b/src/system/vyos-event-handler.py @@ -153,7 +153,12 @@ if __name__ == '__main__': continue for entry in data: message = entry['MESSAGE'] - pid = entry['_PID'] + pid = -1 + try: + pid = entry['_PID'] + except Exception as ex: + journal.send(f'Unable to extract PID from message entry: {entry}', SYSLOG_IDENTIFIER=my_name) + continue # Skip empty messages and messages from this process if message and pid != my_pid: try: |