diff options
30 files changed, 165 insertions, 97 deletions
diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2 index e9422b257..e5bfad59d 100644 --- a/data/templates/frr/bgpd.frr.j2 +++ b/data/templates/frr/bgpd.frr.j2 @@ -290,10 +290,7 @@ router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }} {% endif %} {% if afi_config.aggregate_address is vyos_defined %} {% for aggregate, aggregate_config in afi_config.aggregate_address.items() %} - aggregate-address {{ aggregate }}{{ ' as-set' if aggregate_config.as_set is vyos_defined }}{{ ' summary-only' if aggregate_config.summary_only is vyos_defined }} -{% if aggregate_config.route_map is vyos_defined %} - aggregate-address {{ aggregate }} route-map {{ aggregate_config.route_map }} -{% endif %} + aggregate-address {{ aggregate }} {{ 'as-set' if aggregate_config.as_set is vyos_defined }} {{ 'summary-only' if aggregate_config.summary_only is vyos_defined }} {{ 'route-map ' ~ aggregate_config.route_map if aggregate_config.route_map is vyos_defined }} {% endfor %} {% endif %} {% if afi_config.maximum_paths.ebgp is vyos_defined %} @@ -537,6 +534,9 @@ router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }} {% if parameters.allow_martian_nexthop is vyos_defined %} bgp allow-martian-nexthop {% endif %} +{% if parameters.disable_ebgp_connected_route_check is vyos_defined %} + bgp disable-ebgp-connected-route-check +{% endif %} {% if parameters.always_compare_med is vyos_defined %} bgp always-compare-med {% endif %} diff --git a/data/templates/ssh/override.conf.j2 b/data/templates/ssh/override.conf.j2 deleted file mode 100644 index 4454ad1b8..000000000 --- a/data/templates/ssh/override.conf.j2 +++ /dev/null @@ -1,14 +0,0 @@ -{% set vrf_command = 'ip vrf exec ' ~ vrf ~ ' ' if vrf is vyos_defined else '' %} -[Unit] -StartLimitIntervalSec=0 -After=vyos-router.service -ConditionPathExists={{ config_file }} - -[Service] -EnvironmentFile= -ExecStart= -ExecStart={{ vrf_command }}/usr/sbin/sshd -f {{ config_file }} -Restart=always -RestartPreventExitStatus= -RestartSec=10 -RuntimeDirectoryPreserve=yes diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index 0e6e3c863..78e895d6e 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -194,3 +194,10 @@ systemctl enable vyos-config-cloud-init.service # Update XML cache python3 /usr/lib/python3/dist-packages/vyos/xml_ref/update_cache.py + +# Generate hardlinks for systemd units for multi VRF support +# as softlinks will fail in systemd: +# symlink target name type "ssh.service" does not match source, rejecting. +if [ ! -f /lib/systemd/system/ssh@.service ]; then + ln /lib/systemd/system/ssh.service /lib/systemd/system/ssh@.service +fi diff --git a/interface-definitions/include/bgp/neighbor-disable-connected-check.xml.i b/interface-definitions/include/bgp/neighbor-disable-connected-check.xml.i index cb8b610b4..aef5a55e9 100644 --- a/interface-definitions/include/bgp/neighbor-disable-connected-check.xml.i +++ b/interface-definitions/include/bgp/neighbor-disable-connected-check.xml.i @@ -1,7 +1,7 @@ <!-- include start from bgp/neighbor-disable-connected-check.xml.i --> <leafNode name="disable-connected-check"> <properties> - <help>Disable check to see if eBGP peer address is a connected route</help> + <help>Allow peerings between eBGP peer using loopback/dummy address</help> <valueless/> </properties> </leafNode> diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index ca67eaf3c..0f05625a7 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -1249,6 +1249,12 @@ <valueless/> </properties> </leafNode> + <leafNode name="disable-ebgp-connected-route-check"> + <properties> + <help>Disable checking if nexthop is connected on eBGP session</help> + <valueless/> + </properties> + </leafNode> <leafNode name="always-compare-med"> <properties> <help>Always compare MEDs from different neighbors</help> diff --git a/interface-definitions/include/constraint/vrf.xml.i b/interface-definitions/include/constraint/vrf.xml.i new file mode 100644 index 000000000..a1922bb6d --- /dev/null +++ b/interface-definitions/include/constraint/vrf.xml.i @@ -0,0 +1,6 @@ +<!-- include start from constraint/vrf.xml.i --> +<constraint> + <validator name="vrf-name"/> +</constraint> +<constraintErrorMessage>VRF instance name must be 15 characters or less and can not\nbe named as regular network interfaces.\nA name must starts from a letter.\n</constraintErrorMessage> +<!-- include end --> diff --git a/interface-definitions/include/interface/vrf.xml.i b/interface-definitions/include/interface/vrf.xml.i index 8605f56e8..ef0058f86 100644 --- a/interface-definitions/include/interface/vrf.xml.i +++ b/interface-definitions/include/interface/vrf.xml.i @@ -9,6 +9,7 @@ <completionHelp> <path>vrf name</path> </completionHelp> + #include <include/constraint/vrf.xml.i> </properties> </leafNode> <!-- include end --> diff --git a/interface-definitions/include/vrf-multi.xml.i b/interface-definitions/include/vrf-multi.xml.i new file mode 100644 index 000000000..0b22894e4 --- /dev/null +++ b/interface-definitions/include/vrf-multi.xml.i @@ -0,0 +1,22 @@ +<!-- include start from interface/vrf.xml.i --> +<leafNode name="vrf"> + <properties> + <help>VRF instance name</help> + <completionHelp> + <path>vrf name</path> + <list>default</list> + </completionHelp> + <valueHelp> + <format>default</format> + <description>Explicitly start in default VRF</description> + </valueHelp> + <valueHelp> + <format>txt</format> + <description>VRF instance name</description> + </valueHelp> + #include <include/constraint/vrf.xml.i> + <multi/> + </properties> + <defaultValue>default</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/service_ssh.xml.in b/interface-definitions/service_ssh.xml.in index 5c893bd35..d9eee1ab8 100644 --- a/interface-definitions/service_ssh.xml.in +++ b/interface-definitions/service_ssh.xml.in @@ -262,7 +262,7 @@ </constraint> </properties> </leafNode> - #include <include/interface/vrf.xml.i> + #include <include/vrf-multi.xml.i> </children> </node> </children> diff --git a/interface-definitions/vrf.xml.in b/interface-definitions/vrf.xml.in index 25f26d0cc..94ed96e4b 100644 --- a/interface-definitions/vrf.xml.in +++ b/interface-definitions/vrf.xml.in @@ -16,10 +16,7 @@ <tagNode name="name"> <properties> <help>Virtual Routing and Forwarding instance</help> - <constraint> - <validator name="vrf-name"/> - </constraint> - <constraintErrorMessage>VRF instance name must be 15 characters or less and can not\nbe named as regular network interfaces.\nA name must starts from a letter.\n</constraintErrorMessage> + #include <include/constraint/vrf.xml.i> <valueHelp> <format>txt</format> <description>VRF instance name</description> diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 2a5452e7b..300647d21 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -99,10 +99,17 @@ def verify_vrf(config): Common helper function used by interface implementations to perform recurring validation of VRF configuration. """ - from netifaces import interfaces - if 'vrf' in config and config['vrf'] != 'default': - if config['vrf'] not in interfaces(): - raise ConfigError('VRF "{vrf}" does not exist'.format(**config)) + from vyos.utils.network import interface_exists + if 'vrf' in config: + vrfs = config['vrf'] + if isinstance(vrfs, str): + vrfs = [vrfs] + + for vrf in vrfs: + if vrf == 'default': + continue + if not interface_exists(vrf): + raise ConfigError(f'VRF "{vrf}" does not exist!') if 'is_bridge_member' in config: raise ConfigError( @@ -169,13 +176,13 @@ def verify_mirror_redirect(config): It makes no sense to mirror traffic back at yourself! """ - import os + from vyos.utils.network import interface_exists if {'mirror', 'redirect'} <= set(config): raise ConfigError('Mirror and redirect can not be enabled at the same time!') if 'mirror' in config: for direction, mirror_interface in config['mirror'].items(): - if not os.path.exists(f'/sys/class/net/{mirror_interface}'): + if not interface_exists(mirror_interface): raise ConfigError(f'Requested mirror interface "{mirror_interface}" '\ 'does not exist!') @@ -185,7 +192,7 @@ def verify_mirror_redirect(config): if 'redirect' in config: redirect_ifname = config['redirect'] - if not os.path.exists(f'/sys/class/net/{redirect_ifname}'): + if not interface_exists(redirect_ifname): raise ConfigError(f'Requested redirect interface "{redirect_ifname}" '\ 'does not exist!') @@ -243,6 +250,7 @@ def verify_interface_exists(ifname, warning_only=False): from vyos.base import Warning from vyos.configquery import ConfigTreeQuery from vyos.utils.dict import dict_search_recursive + from vyos.utils.network import interface_exists # Check if interface is present in CLI config config = ConfigTreeQuery() @@ -251,7 +259,7 @@ def verify_interface_exists(ifname, warning_only=False): return True # Interface not found on CLI, try Linux Kernel - if os.path.exists(f'/sys/class/net/{ifname}'): + if interface_exists(ifname): return True message = f'Interface "{ifname}" does not exist!' diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index e70b4f0d9..e29aeb0c6 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -66,7 +66,7 @@ def fqdn_config_parse(firewall): rule = path[4] suffix = path[5][0] set_name = f'{hook_name}_{priority}_{rule}_{suffix}' - + if (path[0] == 'ipv4') and (path[1] == 'forward' or path[1] == 'input' or path[1] == 'output' or path[1] == 'name'): firewall['ip_fqdn'][set_name] = domain elif (path[0] == 'ipv6') and (path[1] == 'forward' or path[1] == 'input' or path[1] == 'output' or path[1] == 'name'): @@ -85,7 +85,7 @@ def fqdn_resolve(fqdn, ipv6=False): def find_nftables_rule(table, chain, rule_matches=[]): # Find rule in table/chain that matches all criteria and return the handle - results = cmd(f'sudo nft -a list chain {table} {chain}').split("\n") + results = cmd(f'sudo nft --handle list chain {table} {chain}').split("\n") for line in results: if all(rule_match in line for rule_match in rule_matches): handle_search = re.search('handle (\d+)', line) @@ -655,7 +655,7 @@ def geoip_update(firewall, force=False): 'ipv6_sets': ipv6_sets }) - result = run(f'nft -f {nftables_geoip_conf}') + result = run(f'nft --file {nftables_geoip_conf}') if result != 0: print('Error: GeoIP failed to update firewall') return False diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index c87fb9c71..b2cb621bc 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -415,7 +415,7 @@ class Interface(Control): else: nft_del_element = f'delete element inet vrf_zones ct_iface_map {{ "{self.ifname}" }}' # Check if deleting is possible first to avoid raising errors - _, err = self._popen(f'nft -c {nft_del_element}') + _, err = self._popen(f'nft --check {nft_del_element}') if not err: # Remove map element self._cmd(f'nft {nft_del_element}') diff --git a/python/vyos/template.py b/python/vyos/template.py index 1aa9ace8b..3e468eb82 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -307,7 +307,8 @@ def network_from_ipv4(address): @register_filter('is_interface') def is_interface(interface): """ Check if parameter is a valid local interface name """ - return os.path.exists(f'/sys/class/net/{interface}') + from vyos.utils.network import interface_exists + return interface_exists(interface) @register_filter('is_ip') def is_ip(addr): diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index e8556cf44..60c49b8b4 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -321,6 +321,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): tcp_keepalive_probes = '22' self.cli_set(base_path + ['parameters', 'allow-martian-nexthop']) + self.cli_set(base_path + ['parameters', 'disable-ebgp-connected-route-check']) self.cli_set(base_path + ['parameters', 'no-hard-administrative-reset']) self.cli_set(base_path + ['parameters', 'log-neighbor-changes']) self.cli_set(base_path + ['parameters', 'labeled-unicast', 'explicit-null']) @@ -372,6 +373,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' bgp router-id {router_id}', frrconfig) self.assertIn(f' bgp allow-martian-nexthop', frrconfig) + self.assertIn(f' bgp disable-ebgp-connected-route-check', frrconfig) self.assertIn(f' bgp log-neighbor-changes', frrconfig) self.assertIn(f' bgp default local-preference {local_pref}', frrconfig) self.assertIn(f' bgp conditional-advertisement timer {cond_adv_timer}', frrconfig) @@ -628,6 +630,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): networks = { '10.0.0.0/8' : { 'as_set' : '', + 'summary_only' : '', + 'route_map' : route_map_in, }, '100.64.0.0/10' : { 'as_set' : '', @@ -652,6 +656,9 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'summary_only' in network_config: self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'aggregate-address', network, 'summary-only']) + if 'route_map' in network_config: + self.cli_set(base_path + ['address-family', 'ipv4-unicast', + 'aggregate-address', network, 'route-map', network_config['route_map']]) # commit changes self.cli_commit() @@ -666,10 +673,14 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): for network, network_config in networks.items(): self.assertIn(f' network {network}', frrconfig) + command = f'aggregate-address {network}' if 'as_set' in network_config: - self.assertIn(f' aggregate-address {network} as-set', frrconfig) + command = f'{command} as-set' if 'summary_only' in network_config: - self.assertIn(f' aggregate-address {network} summary-only', frrconfig) + command = f'{command} summary-only' + if 'route_map' in network_config: + command = f'{command} route-map {network_config["route_map"]}' + self.assertIn(command, frrconfig) def test_bgp_05_afi_ipv6(self): networks = { diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py index 947d7d568..031897c26 100755 --- a/smoketest/scripts/cli/test_service_ssh.py +++ b/smoketest/scripts/cli/test_service_ssh.py @@ -32,7 +32,6 @@ from vyos.utils.file import read_file PROCESS_NAME = 'sshd' SSHD_CONF = '/run/sshd/sshd_config' base_path = ['service', 'ssh'] -vrf = 'mgmt' key_rsa = '/etc/ssh/ssh_host_rsa_key' key_dsa = '/etc/ssh/ssh_host_dsa_key' @@ -51,6 +50,7 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) + cls.cli_delete(cls, ['vrf']) def tearDown(self): # Check for running process @@ -58,6 +58,7 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): # delete testing SSH config self.cli_delete(base_path) + self.cli_delete(['vrf']) self.cli_commit() self.assertTrue(os.path.isfile(key_rsa)) @@ -79,7 +80,7 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): # Check configured port port = get_config_value('Port')[0] - self.assertEqual('22', port) + self.assertEqual('22', port) # default value def test_ssh_single_listen_address(self): # Check if SSH service can be configured and runs @@ -141,10 +142,9 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): for address in addresses: self.assertIn(address, tmp) - def test_ssh_vrf(self): + def test_ssh_vrf_single(self): + vrf = 'mgmt' # Check if SSH service can be bound to given VRF - port = '22' - self.cli_set(base_path + ['port', port]) self.cli_set(base_path + ['vrf', vrf]) # VRF does yet not exist - an error must be thrown @@ -156,16 +156,32 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): # commit changes self.cli_commit() - # Check configured port - tmp = get_config_value('Port') - self.assertIn(port, tmp) - # Check for process in VRF tmp = cmd(f'ip vrf pids {vrf}') self.assertIn(PROCESS_NAME, tmp) - # delete VRF - self.cli_delete(['vrf', 'name', vrf]) + def test_ssh_vrf_multi(self): + # Check if SSH service can be bound to multiple VRFs + vrfs = ['red', 'blue', 'green'] + for vrf in vrfs: + self.cli_set(base_path + ['vrf', vrf]) + + # VRF does yet not exist - an error must be thrown + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + table = 12345 + for vrf in vrfs: + self.cli_set(['vrf', 'name', vrf, 'table', str(table)]) + table += 1 + + # commit changes + self.cli_commit() + + # Check for process in VRF + for vrf in vrfs: + tmp = cmd(f'ip vrf pids {vrf}') + self.assertIn(PROCESS_NAME, tmp) def test_ssh_login(self): # Perform SSH login and command execution with a predefined user. The diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index e967bee71..910a92a7c 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -32,6 +32,7 @@ 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.network import interface_exists from vyos.template import bracketize_ipv6 from vyos.template import inc_ip from vyos.template import is_ipv4 @@ -471,7 +472,7 @@ def apply(container): # T5147: Networks are started only as soon as there is a consumer. # If only a network is created in the first place, no need to assign # it to a VRF as there's no consumer, yet. - if os.path.exists(f'/sys/class/net/{network_name}'): + if interface_exists(network_name): tmp = Interface(network_name) tmp.add_ipv6_eui64_address('fe80::/64') tmp.set_vrf(network_config.get('vrf', '')) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 810437dda..3cf618363 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.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 @@ -18,7 +18,6 @@ import os import re from glob import glob -from json import loads from sys import exit from vyos.base import Warning @@ -31,11 +30,9 @@ from vyos.ethtool import Ethtool from vyos.firewall import fqdn_config_parse from vyos.firewall import geoip_update from vyos.template import render -from vyos.utils.process import call -from vyos.utils.process import cmd from vyos.utils.dict import dict_search_args from vyos.utils.dict import dict_search_recursive -from vyos.utils.process import process_named_running +from vyos.utils.process import call from vyos.utils.process import rc_cmd from vyos import ConfigError from vyos import airbag @@ -491,7 +488,7 @@ def apply_sysfs(firewall): f.write(value) def apply(firewall): - install_result, output = rc_cmd(f'nft -f {nftables_conf}') + install_result, output = rc_cmd(f'nft --file {nftables_conf}') if install_result == 1: raise ConfigError(f'Failed to apply firewall: {output}') diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index b3f38c04a..76c07a9ec 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -223,19 +223,19 @@ def generate(nat): render(nftables_static_nat_conf, 'firewall/nftables-static-nat.j2', nat) # dry-run newly generated configuration - tmp = run(f'nft -c -f {nftables_nat_config}') + tmp = run(f'nft --check --file {nftables_nat_config}') if tmp > 0: raise ConfigError('Configuration file errors encountered!') - tmp = run(f'nft -c -f {nftables_static_nat_conf}') + tmp = run(f'nft --check --file {nftables_static_nat_conf}') if tmp > 0: raise ConfigError('Configuration file errors encountered!') return None def apply(nat): - cmd(f'nft -f {nftables_nat_config}') - cmd(f'nft -f {nftables_static_nat_conf}') + cmd(f'nft --file {nftables_nat_config}') + cmd(f'nft --file {nftables_static_nat_conf}') if not nat or 'deleted' in nat: os.unlink(nftables_nat_config) diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py index 4c1ead258..fe017527d 100755 --- a/src/conf_mode/nat66.py +++ b/src/conf_mode/nat66.py @@ -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 @@ -14,8 +14,6 @@ # 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 jmespath -import json import os from sys import exit @@ -106,7 +104,7 @@ def apply(nat): if not nat: return None - cmd(f'nft -f {nftables_nat66_config}') + cmd(f'nft --file {nftables_nat66_config}') call_dependents() return None diff --git a/src/conf_mode/policy_route.py b/src/conf_mode/policy_route.py index 6d7a06714..c58fe1bce 100755 --- a/src/conf_mode/policy_route.py +++ b/src/conf_mode/policy_route.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 @@ -177,7 +177,7 @@ def cleanup_table_marks(): cmd(f'{cmd_str} rule del fwmark {fwmark} table {table}') def apply(policy): - install_result = run(f'nft -f {nftables_conf}') + install_result = run(f'nft --file {nftables_conf}') if install_result == 1: raise ConfigError('Failed to apply policy based routing') diff --git a/src/conf_mode/protocols_nhrp.py b/src/conf_mode/protocols_nhrp.py index c339c6391..9f66407f2 100755 --- a/src/conf_mode/protocols_nhrp.py +++ b/src/conf_mode/protocols_nhrp.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 @@ -93,7 +93,7 @@ def generate(nhrp): return None def apply(nhrp): - nft_rc = run(f'nft -f {nhrp_nftables_conf}') + nft_rc = run(f'nft --file {nhrp_nftables_conf}') if nft_rc != 0: raise ConfigError('Failed to apply NHRP tunnel firewall rules') diff --git a/src/conf_mode/qos.py b/src/conf_mode/qos.py index 4a0b4d0c5..2b4fcc1bf 100755 --- a/src/conf_mode/qos.py +++ b/src/conf_mode/qos.py @@ -36,8 +36,9 @@ from vyos.qos import RateLimiter from vyos.qos import RoundRobin from vyos.qos import TrafficShaper from vyos.qos import TrafficShaperHFSC -from vyos.utils.process import run from vyos.utils.dict import dict_search_recursive +from vyos.utils.network import interface_exists +from vyos.utils.process import run from vyos import ConfigError from vyos import airbag airbag.enable() @@ -214,7 +215,7 @@ def apply(qos): return None for interface, interface_config in qos['interface'].items(): - if not os.path.exists(f'/sys/class/net/{interface}'): + if not interface_exists(interface): # When shaper is bound to a dialup (e.g. PPPoE) interface it is # possible that it is yet not availbale when to QoS code runs. # Skip the configuration and inform the user diff --git a/src/conf_mode/service_ssh.py b/src/conf_mode/service_ssh.py index ee5e1eca2..9abdd33dc 100755 --- a/src/conf_mode/service_ssh.py +++ b/src/conf_mode/service_ssh.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2022 VyOS maintainers and contributors +# Copyright (C) 2018-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 @@ -30,7 +30,6 @@ from vyos import airbag airbag.enable() config_file = r'/run/sshd/sshd_config' -systemd_override = r'/run/systemd/system/ssh.service.d/override.conf' sshguard_config_file = '/etc/sshguard/sshguard.conf' sshguard_whitelist = '/etc/sshguard/whitelist' @@ -81,8 +80,6 @@ def generate(ssh): if not ssh: if os.path.isfile(config_file): os.unlink(config_file) - if os.path.isfile(systemd_override): - os.unlink(systemd_override) return None @@ -99,13 +96,10 @@ def generate(ssh): call(f'ssh-keygen -q -N "" -t ed25519 -f {key_ed25519}') render(config_file, 'ssh/sshd_config.j2', ssh) - render(systemd_override, 'ssh/override.conf.j2', ssh) if 'dynamic_protection' in ssh: render(sshguard_config_file, 'ssh/sshguard_config.j2', ssh) render(sshguard_whitelist, 'ssh/sshguard_whitelist.j2', ssh) - # Reload systemd manager configuration - call('systemctl daemon-reload') return None @@ -114,7 +108,7 @@ def apply(ssh): systemd_service_sshguard = 'sshguard.service' if not ssh: # SSH access is removed in the commit - call(f'systemctl stop {systemd_service_ssh}') + call(f'systemctl stop ssh@*.service') call(f'systemctl stop {systemd_service_sshguard}') return None @@ -126,9 +120,13 @@ def apply(ssh): # we need to restart the service if e.g. the VRF name changed systemd_action = 'reload-or-restart' if 'restart_required' in ssh: + # this is only true if something for the VRFs changed, thus we + # stop all VRF services and only restart then new ones + call(f'systemctl stop ssh@*.service') systemd_action = 'restart' - call(f'systemctl {systemd_action} {systemd_service_ssh}') + for vrf in ssh['vrf']: + call(f'systemctl {systemd_action} ssh@{vrf}.service') return None if __name__ == '__main__': diff --git a/src/conf_mode/system_conntrack.py b/src/conf_mode/system_conntrack.py index 3d42389f6..031fe63b0 100755 --- a/src/conf_mode/system_conntrack.py +++ b/src/conf_mode/system_conntrack.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 @@ -15,19 +15,16 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import os -import re from sys import exit from vyos.config import Config from vyos.configdep import set_dependents, call_dependents -from vyos.utils.process import process_named_running from vyos.utils.dict import dict_search from vyos.utils.dict import dict_search_args from vyos.utils.dict import dict_search_recursive from vyos.utils.process import cmd from vyos.utils.process import rc_cmd -from vyos.utils.process import run from vyos.template import render from vyos import ConfigError from vyos import airbag @@ -223,7 +220,7 @@ def apply(conntrack): cmd(f'modprobe -a {module_str}') # Load new nftables ruleset - install_result, output = rc_cmd(f'nft -f {nftables_ct_file}') + install_result, output = rc_cmd(f'nft --file {nftables_ct_file}') if install_result == 1: raise ConfigError(f'Failed to apply configuration: {output}') diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 16908100f..1fc813189 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -14,8 +14,6 @@ # 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 - from sys import exit from json import loads @@ -33,6 +31,7 @@ from vyos.utils.network import get_vrf_members from vyos.utils.network import interface_exists from vyos.utils.process import call from vyos.utils.process import cmd +from vyos.utils.process import popen from vyos.utils.system import sysctl_write from vyos import ConfigError from vyos import frr @@ -227,7 +226,11 @@ def apply(vrf): # Remove nftables conntrack zone map item nft_del_element = f'delete element inet vrf_zones ct_iface_map {{ "{tmp}" }}' - cmd(f'nft {nft_del_element}') + # Check if deleting is possible first to avoid raising errors + _, err = popen(f'nft --check {nft_del_element}') + if not err: + # Remove map element + cmd(f'nft {nft_del_element}') # Delete the VRF Kernel interface call(f'ip link delete dev {tmp}') @@ -307,7 +310,7 @@ def apply(vrf): 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}') diff --git a/src/etc/systemd/system/ssh@.service.d/vrf-override.conf b/src/etc/systemd/system/ssh@.service.d/vrf-override.conf new file mode 100644 index 000000000..b8952d86c --- /dev/null +++ b/src/etc/systemd/system/ssh@.service.d/vrf-override.conf @@ -0,0 +1,13 @@ +[Unit] +StartLimitIntervalSec=0 +After=vyos-router.service +ConditionPathExists=/run/sshd/sshd_config + +[Service] +EnvironmentFile= +ExecStart= +ExecStart=ip vrf exec %i /usr/sbin/sshd -f /run/sshd/sshd_config +Restart=always +RestartPreventExitStatus= +RestartSec=10 +RuntimeDirectoryPreserve=yes diff --git a/src/helpers/vyos-domain-resolver.py b/src/helpers/vyos-domain-resolver.py index eac3d37af..57cfcabd7 100755 --- a/src/helpers/vyos-domain-resolver.py +++ b/src/helpers/vyos-domain-resolver.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022-2023 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 @@ -15,7 +15,6 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import json -import os import time from vyos.configdict import dict_merge @@ -95,7 +94,7 @@ def nft_output(table, set_name, ip_list): def nft_valid_sets(): try: valid_sets = [] - sets_json = cmd('nft -j list sets') + sets_json = cmd('nft --json list sets') sets_obj = json.loads(sets_json) for obj in sets_obj['nftables']: @@ -155,7 +154,7 @@ def update(firewall): count += 1 nft_conf_str = "\n".join(conf_lines) + "\n" - code = run(f'nft -f -', input=nft_conf_str) + code = run(f'nft --file -', input=nft_conf_str) print(f'Updated {count} sets - result: {code}') diff --git a/src/init/vyos-router b/src/init/vyos-router index adf892371..06fea140d 100755 --- a/src/init/vyos-router +++ b/src/init/vyos-router @@ -430,7 +430,7 @@ start () nfct helper add rpc inet6 tcp nfct helper add rpc inet6 udp nfct helper add tns inet6 tcp - nft -f /usr/share/vyos/vyos-firewall-init.conf || log_failure_msg "could not initiate firewall rules" + nft --file /usr/share/vyos/vyos-firewall-init.conf || log_failure_msg "could not initiate firewall rules" # As VyOS does not execute commands that are not present in the CLI we call # the script by hand to have a single source for the login banner and MOTD diff --git a/src/tests/test_template.py b/src/tests/test_template.py index aba97015e..dbb86b40b 100644 --- a/src/tests/test_template.py +++ b/src/tests/test_template.py @@ -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 @@ -14,9 +14,9 @@ # 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 vyos.template +from vyos.utils.network import interface_exists from ipaddress import ip_network from unittest import TestCase @@ -26,7 +26,7 @@ class TestVyOSTemplate(TestCase): def test_is_interface(self): for interface in ['lo', 'eth0']: - if os.path.exists(f'/sys/class/net/{interface}'): + if interface_exists(interface): self.assertTrue(vyos.template.is_interface(interface)) else: self.assertFalse(vyos.template.is_interface(interface)) |