diff --git a/data/templates/frr/daemons.frr.tmpl b/data/templates/frr/daemons.frr.tmpl index a65f0868a..c637e18bc 100644 --- a/data/templates/frr/daemons.frr.tmpl +++ b/data/templates/frr/daemons.frr.tmpl @@ -1,114 +1,113 @@ # # The watchfrr, zebra, mgmtd and staticd daemons are always started. # # Note: The following FRR-services must be kept disabled because they are replaced by other packages in VyOS: # # pimd Replaced by package igmpproxy. # nhrpd Replaced by package opennhrp. # pbrd Replaced by PBR in nftables. # vrrpd Replaced by package keepalived. # # And these must be disabled aswell since they are currently missing a VyOS CLI: # # eigrp # sharpd # fabricd # pathd # # The zebra, mgmtd and staticd daemons are always started and can not be disabled # #zebra=yes #mgmtd=yes #staticd=yes bgpd=yes ospfd=yes ospf6d=yes ripd=yes ripngd=yes isisd=yes pimd=no pim6d=yes ldpd=yes nhrpd=no eigrpd=no babeld=yes sharpd=no pbrd=no bfdd=yes fabricd=no vrrpd=no pathd=no # # Define defaults for all services even those who shall be kept disabled. # zebra_options=" --daemon -A 127.0.0.1 -s 90000000{{ ' -M snmp' if snmp.zebra is vyos_defined }}{{ ' -M irdp' if irdp is vyos_defined }}" mgmtd_options=" --daemon -A 127.0.0.1" staticd_options="--daemon -A 127.0.0.1" bgpd_options=" --daemon -A 127.0.0.1 -M rpki{{ ' -M snmp' if snmp.bgpd is vyos_defined }}{{ ' -M bmp' if bmp is vyos_defined }}" ospfd_options=" --daemon -A 127.0.0.1{{ ' -M snmp' if snmp.ospfd is vyos_defined }}" ospf6d_options=" --daemon -A ::1{{ ' -M snmp' if snmp.ospf6d is vyos_defined }}" ripd_options=" --daemon -A 127.0.0.1{{ ' -M snmp' if snmp.ripd is vyos_defined }}" ripngd_options=" --daemon -A ::1" isisd_options=" --daemon -A 127.0.0.1{{ ' -M snmp' if snmp.isisd is vyos_defined }}" pimd_options=" --daemon -A 127.0.0.1" pim6d_options=" --daemon -A ::1" ldpd_options=" --daemon -A 127.0.0.1{{ ' -M snmp' if snmp.ldpd is vyos_defined }}" nhrpd_options=" --daemon -A 127.0.0.1" eigrpd_options=" --daemon -A 127.0.0.1" babeld_options=" --daemon -A 127.0.0.1" sharpd_options=" --daemon -A 127.0.0.1" pbrd_options=" --daemon -A 127.0.0.1" bfdd_options=" --daemon -A 127.0.0.1" fabricd_options="--daemon -A 127.0.0.1" vrrpd_options=" --daemon -A 127.0.0.1" pathd_options=" --daemon -A 127.0.0.1" #frr_global_options="" #zebra_wrap="" #mgmtd_wrap="" #staticd_wrap="" #bgpd_wrap="" #ospfd_wrap="" #ospf6d_wrap="" #ripd_wrap="" #ripngd_wrap="" #isisd_wrap="" #pimd_wrap="" #pim6d_wrap="" #ldpd_wrap="" #nhrpd_wrap="" #eigrpd_wrap="" #babeld_wrap="" #sharpd_wrap="" #pbrd_wrap="" #bfdd_wrap="" #fabricd_wrap="" #vrrpd_wrap="" #pathd_wrap="" #all_wrap="" # # Other options. # # For more information see: # https://github.com/FRRouting/frr/blob/stable/9.0/tools/etc/frr/daemons # https://docs.frrouting.org/en/stable-9.0/setup.html # vtysh_enable=yes watchfrr_enable=no valgrind_enable=no #watchfrr_options="" frr_profile="traditional" -#MAX_FDS=1024 +MAX_FDS={{ descriptors }} #FRR_NO_ROOT="yes" - diff --git a/interface-definitions/system-frr.xml.in b/interface-definitions/system-frr.xml.in index 9fe23ed75..76001b392 100644 --- a/interface-definitions/system-frr.xml.in +++ b/interface-definitions/system-frr.xml.in @@ -1,77 +1,91 @@ <?xml version="1.0" encoding="UTF-8"?> <interfaceDefinition> <node name="system"> <children> <node name="frr" owner="${vyos_conf_scripts_dir}/system_frr.py"> <properties> <help>Configure FRR parameters</help> <!-- Before components that use FRR --> <priority>150</priority> </properties> <children> <leafNode name="bmp"> <properties> <help>Enable BGP Monitoring Protocol support</help> <valueless/> </properties> </leafNode> + <leafNode name="descriptors"> + <properties> + <help>Number of open file descriptors a process is allowed to use</help> + <valueHelp> + <format>u32:1024-8192</format> + <description>Number of file descriptors</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1024-8192"/> + </constraint> + <constraintErrorMessage>Port number must be in range 1024 to 8192</constraintErrorMessage> + </properties> + <defaultValue>1024</defaultValue> + </leafNode> <leafNode name="irdp"> <properties> <help>Enable ICMP Router Discovery Protocol support</help> <valueless/> </properties> </leafNode> <node name="snmp"> <properties> <help>Enable SNMP integration for next daemons</help> </properties> <children> <leafNode name="bgpd"> <properties> <help>BGP</help> <valueless/> </properties> </leafNode> <leafNode name="isisd"> <properties> <help>IS-IS</help> <valueless/> </properties> </leafNode> <leafNode name="ldpd"> <properties> <help>LDP</help> <valueless/> </properties> </leafNode> <leafNode name="ospf6d"> <properties> <help>OSPFv3</help> <valueless/> </properties> </leafNode> <leafNode name="ospfd"> <properties> <help>OSPFv2</help> <valueless/> </properties> </leafNode> <leafNode name="ripd"> <properties> <help>RIP</help> <valueless/> </properties> </leafNode> <leafNode name="zebra"> <properties> <help>Zebra (IP routing manager)</help> <valueless/> </properties> </leafNode> </children> </node> </children> </node> </children> </node> </interfaceDefinition> diff --git a/smoketest/scripts/cli/test_system_frr.py b/smoketest/scripts/cli/test_system_frr.py index 3eb0cd0ab..a2ce58bf6 100755 --- a/smoketest/scripts/cli/test_system_frr.py +++ b/smoketest/scripts/cli/test_system_frr.py @@ -1,146 +1,162 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2021-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 # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import re import unittest + from base_vyostest_shim import VyOSUnitTestSHIM from vyos.utils.file import read_file config_file = '/etc/frr/daemons' base_path = ['system', 'frr'] - def daemons_config_parse(daemons_config): # create regex for parsing daemons options regex_daemon_config = re.compile( r'^(?P<daemon_name>\w+)_options="(?P<daemon_options>.*)"$', re.M) # create empty dict for config daemons_config_dict = {} # fill dictionary with actual config for daemon in regex_daemon_config.finditer(daemons_config): daemon_name = daemon.group('daemon_name') daemon_options = daemon.group('daemon_options') - daemons_config_dict[daemon_name] = daemon_options + daemons_config_dict[daemon_name] = daemon_options.lstrip() # return daemons config return (daemons_config_dict) class TestSystemFRR(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestSystemFRR, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) def tearDown(self): self.cli_delete(base_path) self.cli_commit() def test_frr_snmp_multipledaemons(self): # test SNMP integration for multiple daemons test_daemon_names = ['ospfd', 'bgpd'] for test_daemon_name in test_daemon_names: self.cli_set(base_path + ['snmp', test_daemon_name]) self.cli_commit() # read the config file and check content daemons_config = read_file(config_file) daemons_config_dict = daemons_config_parse(daemons_config) # prepare regex for matching SNMP integration regex_snmp = re.compile(r'^.* -M snmp.*$') for (daemon_name, daemon_options) in daemons_config_dict.items(): snmp_enabled = regex_snmp.match(daemon_options) if daemon_name in test_daemon_names: self.assertTrue(snmp_enabled) else: self.assertFalse(snmp_enabled) - def test_frr_snmp_addandremove(self): + def test_frr_snmp_add_remove(self): # test enabling and disabling of SNMP integration test_daemon_names = ['ospfd', 'bgpd'] for test_daemon_name in test_daemon_names: self.cli_set(base_path + ['snmp', test_daemon_name]) self.cli_commit() self.cli_delete(base_path) self.cli_commit() # read the config file and check content daemons_config = read_file(config_file) daemons_config_dict = daemons_config_parse(daemons_config) # prepare regex for matching SNMP integration regex_snmp = re.compile(r'^.* -M snmp.*$') for test_daemon_name in test_daemon_names: snmp_enabled = regex_snmp.match( daemons_config_dict[test_daemon_name]) self.assertFalse(snmp_enabled) def test_frr_snmp_empty(self): # test empty config section self.cli_set(base_path + ['snmp']) self.cli_commit() # read the config file and check content daemons_config = read_file(config_file) daemons_config_dict = daemons_config_parse(daemons_config) # prepare regex for matching SNMP integration regex_snmp = re.compile(r'^.* -M snmp.*$') for daemon_options in daemons_config_dict.values(): snmp_enabled = regex_snmp.match(daemon_options) self.assertFalse(snmp_enabled) def test_frr_bmp(self): # test BMP self.cli_set(base_path + ['bmp']) self.cli_commit() # read the config file and check content daemons_config = read_file(config_file) daemons_config_dict = daemons_config_parse(daemons_config) # prepare regex regex_bmp = re.compile(r'^.* -M bmp.*$') bmp_enabled = regex_bmp.match(daemons_config_dict['bgpd']) self.assertTrue(bmp_enabled) def test_frr_irdp(self): # test IRDP self.cli_set(base_path + ['irdp']) self.cli_commit() # read the config file and check content daemons_config = read_file(config_file) daemons_config_dict = daemons_config_parse(daemons_config) # prepare regex regex_irdp = re.compile(r'^.* -M irdp.*$') irdp_enabled = regex_irdp.match(daemons_config_dict['zebra']) self.assertTrue(irdp_enabled) - def test_frr_bmpandsnmp(self): + def test_frr_bmp_and_snmp(self): # test empty config section self.cli_set(base_path + ['bmp']) self.cli_set(base_path + ['snmp', 'bgpd']) self.cli_commit() # read the config file and check content daemons_config = read_file(config_file) daemons_config_dict = daemons_config_parse(daemons_config) # prepare regex regex_snmp = re.compile(r'^.* -M bmp.*$') regex_snmp = re.compile(r'^.* -M snmp.*$') bmp_enabled = regex_snmp.match(daemons_config_dict['bgpd']) snmp_enabled = regex_snmp.match(daemons_config_dict['bgpd']) self.assertTrue(bmp_enabled) self.assertTrue(snmp_enabled) + def test_frr_file_descriptors(self): + file_descriptors = '4096' + + self.cli_set(base_path + ['descriptors', file_descriptors]) + self.cli_commit() + + # read the config file and check content + daemons_config = read_file(config_file) + self.assertIn(f'MAX_FDS={file_descriptors}', daemons_config) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/system_frr.py b/src/conf_mode/system_frr.py index d8224b3c3..d43b172a6 100755 --- a/src/conf_mode/system_frr.py +++ b/src/conf_mode/system_frr.py @@ -1,84 +1,86 @@ #!/usr/bin/env python3 # # Copyright (C) 2021-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 # 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/>. from pathlib import Path from sys import exit from vyos import ConfigError from vyos import airbag from vyos.config import Config from vyos.logger import syslog from vyos.template import render_to_string from vyos.utils.boot import boot_configuration_complete from vyos.utils.file import read_file from vyos.utils.file import write_file from vyos.utils.process import call airbag.enable() # path to daemons config and config status files config_file = '/etc/frr/daemons' def get_config(config=None): if config: conf = config else: conf = Config() base = ['system', 'frr'] - frr_config = conf.get_config_dict(base, get_first_key=True) + frr_config = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) return frr_config def verify(frr_config): # Nothing to verify here pass def generate(frr_config): # read daemons config file daemons_config_current = read_file(config_file) # generate new config file daemons_config_new = render_to_string('frr/daemons.frr.tmpl', frr_config) # update configuration file if this is necessary if daemons_config_new != daemons_config_current: syslog.warning('FRR daemons configuration file need to be changed') write_file(config_file, daemons_config_new) frr_config['config_file_changed'] = True def apply(frr_config): # display warning to user if boot_configuration_complete() and frr_config.get('config_file_changed'): # Since FRR restart is not safe thing, better to give # control over this to users print(''' You need to reboot a router (preferred) or restart FRR to apply changes in modules settings ''') # restart FRR automatically # During initial boot this should be safe in most cases if not boot_configuration_complete() and frr_config.get('config_file_changed'): syslog.warning('Restarting FRR to apply changes in modules') call(f'systemctl restart frr.service') if __name__ == '__main__': try: c = get_config() verify(c) generate(c) apply(c) except ConfigError as e: print(e) exit(1)