diff --git a/data/templates/frr/pimd.frr.j2 b/data/templates/frr/pimd.frr.j2 index 97c5ff58b..b01fb5ec7 100644 --- a/data/templates/frr/pimd.frr.j2 +++ b/data/templates/frr/pimd.frr.j2 @@ -1,92 +1,95 @@ {% if interface is vyos_defined %} {% for iface, iface_config in interface.items() %} ! interface {{ iface }} ip pim {% if iface_config.bfd is vyos_defined %} ip pim bfd {{ 'profile ' ~ iface_config.bfd.profile if iface_config.bfd.profile is vyos_defined }} {% endif %} {% if iface_config.no_bsm is vyos_defined %} no ip pim bsm {% endif %} {% if iface_config.dr_priority is vyos_defined %} ip pim drpriority {{ iface_config.dr_priority }} {% endif %} {% if iface_config.hello is vyos_defined %} ip pim hello {{ iface_config.hello }} {% endif %} {% if iface_config.no_unicast_bsm is vyos_defined %} no ip pim unicast-bsm {% endif %} {% if iface_config.passive is vyos_defined %} ip pim passive {% endif %} {% if iface_config.source_address is vyos_defined %} ip pim use-source {{ iface_config.source_address }} {% endif %} {% if iface_config.igmp is vyos_defined %} ip igmp {% endif %} {% if iface_config.igmp.query_interval %} ip igmp query-interval {{ iface_config.igmp.query_interval }} {% endif %} {% if iface_config.igmp.query_max_response_time %} ip igmp query-max-response-time {{ iface_config.igmp.query_max_response_time }} {% endif %} {% if iface_config.igmp.version is vyos_defined %} ip igmp version {{ iface_config.igmp.version }} {% endif %} {% if iface_config.igmp.join is vyos_defined %} {% for join, join_config in iface_config.igmp.join.items() %} {% if join_config.source_address is vyos_defined %} {% for source_address in join_config.source_address %} ip igmp join {{ join }} {{ source_address }} {% endfor %} {% else %} ip igmp join {{ join }} {% endif %} {% endfor %} {% endif %} exit {% endfor %} {% endif %} ! {% if ecmp is vyos_defined %} ip pim ecmp {{ 'rebalance' if ecmp.rebalance is vyos_defined }} {% endif %} {% if join_prune_interval is vyos_defined %} ip pim join-prune-interval {{ join_prune_interval }} {% endif %} {% if keep_alive_timer is vyos_defined %} -ip pim rp keep-alive-timer {{ keep_alive_timer }} +ip pim keep-alive-timer {{ keep_alive_timer }} {% endif %} {% if packets is vyos_defined %} ip pim packets {{ packets }} {% endif %} -{% if register_accept_list is vyos_defined %} -ip pim register-accept-list {{ register_accept_list }} +{% if register_accept_list.prefix_list is vyos_defined %} +ip pim register-accept-list {{ register_accept_list.prefix_list }} {% endif %} {% if register_suppress_time is vyos_defined %} ip pim register-suppress-time {{ register_suppress_time }} {% endif %} {% if rp.address is vyos_defined %} {% for address, address_config in rp.address.items() %} {% for group in address_config.group %} ip pim rp {{ address }} {{ group }} {% endfor %} {% endfor %} {% endif %} -{% if send_v6_secondary is vyos_defined %} -ip pim send-v6-secondary +{% if rp.keep_alive_timer is vyos_defined %} +ip pim rp keep-alive-timer {{ rp.keep_alive_timer }} +{% endif %} +{% if no_v6_secondary is vyos_defined %} +no ip pim send-v6-secondary {% endif %} {% if spt_switchover.infinity_and_beyond is vyos_defined %} ip pim spt-switchover infinity-and-beyond {{ 'prefix-list ' ~ spt_switchover.infinity_and_beyond.prefix_list if spt_switchover.infinity_and_beyond.prefix_list is defined }} {% endif %} -{% if ssm is vyos_defined %} -ip pim ssm {{ ssm }} +{% if ssm.prefix_list is vyos_defined %} +ip pim ssm prefix-list {{ ssm.prefix_list }} {% endif %} ! {% if igmp.watermark_warning is vyos_defined %} ip igmp watermark-warn {{ igmp.watermark_warning }} {% endif %} ! diff --git a/interface-definitions/include/pim/keep-alive-timer.xml.i b/interface-definitions/include/pim/keep-alive-timer.xml.i index 9e71b7a14..0dd27d6e7 100644 --- a/interface-definitions/include/pim/keep-alive-timer.xml.i +++ b/interface-definitions/include/pim/keep-alive-timer.xml.i @@ -1,15 +1,14 @@ <!-- include start from pim/keep-alive-timer.xml.i --> <leafNode name="keep-alive-timer"> <properties> <help>Keep alive Timer</help> <valueHelp> <format>u32:1-65535</format> <description>Keep alive Timer in seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-65535"/> </constraint> </properties> - <defaultValue>210</defaultValue> </leafNode> <!-- include end --> diff --git a/interface-definitions/include/policy/prefix-list.xml.i b/interface-definitions/include/policy/prefix-list.xml.i new file mode 100644 index 000000000..5d7980ee2 --- /dev/null +++ b/interface-definitions/include/policy/prefix-list.xml.i @@ -0,0 +1,14 @@ +<!-- include start from policy/prefix-list.xml.i --> +<leafNode name="prefix-list"> + <properties> + <help>Prefix-list to use</help> + <valueHelp> + <format>txt</format> + <description>Prefix-list to apply (IPv4)</description> + </valueHelp> + <completionHelp> + <path>policy prefix-list</path> + </completionHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/protocols-pim.xml.in b/interface-definitions/protocols-pim.xml.in index c1fa1b489..bbdb00cae 100644 --- a/interface-definitions/protocols-pim.xml.in +++ b/interface-definitions/protocols-pim.xml.in @@ -1,227 +1,209 @@ <?xml version="1.0"?> <!-- Protocol Independent Multicast (PIM) configuration --> <interfaceDefinition> <node name="protocols"> <children> <node name="pim" owner="${vyos_conf_scripts_dir}/protocols_pim.py"> <properties> <help>Protocol Independent Multicast (PIM) and IGMP</help> <priority>400</priority> </properties> <children> <tagNode name="interface"> <properties> <help>PIM interface</help> <completionHelp> <script>${vyos_completion_dir}/list_interfaces</script> </completionHelp> <constraint> #include <include/constraint/interface-name.xml.i> </constraint> </properties> <children> #include <include/bfd/bfd.xml.i> #include <include/pim/bsm.xml.i> #include <include/pim/dr-priority.xml.i> #include <include/pim/hello.xml.i> #include <include/pim/passive.xml.i> #include <include/source-address-ipv4.xml.i> <node name="igmp"> <properties> <help>Internet Group Management Protocol (IGMP) options</help> </properties> <children> <tagNode name="join"> <properties> <help>IGMP join multicast group</help> <valueHelp> <format>ipv4</format> <description>Multicast group address</description> </valueHelp> <constraint> <validator name="ipv4-address"/> </constraint> </properties> <children> #include <include/source-address-ipv4-multi.xml.i> </children> </tagNode> <leafNode name="query-interval"> <properties> <help>IGMP host query interval</help> <valueHelp> <format>u32:1-1800</format> <description>Query interval in seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-1800"/> </constraint> </properties> </leafNode> <leafNode name="query-max-response-time"> <properties> <help>IGMP max query response time</help> <valueHelp> <format>u32:10-250</format> <description>Query response value in deci-seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 10-250"/> </constraint> </properties> </leafNode> <leafNode name="version"> <properties> <help>Interface IGMP version</help> <completionHelp> <list>2 3</list> </completionHelp> <valueHelp> <format>2</format> <description>IGMP version 2</description> </valueHelp> <valueHelp> <format>3</format> <description>IGMP version 3</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 2-3"/> </constraint> </properties> <defaultValue>3</defaultValue> </leafNode> </children> </node> </children> </tagNode> <node name="ecmp"> <properties> <help>Enable PIM ECMP</help> </properties> <children> <leafNode name="rebalance"> <properties> <help>Enable PIM ECMP Rebalance</help> <valueless/> </properties> </leafNode> </children> </node> <node name="igmp"> <properties> <help>Internet Group Management Protocol (IGMP) options</help> </properties> <children> <leafNode name="watermark-warning"> <properties> <help>Configure group limit for watermark warning</help> <valueHelp> <format>u32:1-65535</format> <description>Group count to generate watermark warning</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-65535"/> </constraint> </properties> </leafNode> </children> </node> #include <include/pim/join-prune-interval.xml.i> #include <include/pim/keep-alive-timer.xml.i> #include <include/pim/packets.xml.i> #include <include/pim/register-suppress-time.xml.i> - <leafNode name="register-accept-list"> + <node name="register-accept-list"> <properties> <help>Only accept registers from a specific source prefix list</help> - <valueHelp> - <format>txt</format> - <description>Prefix-list to apply</description> - </valueHelp> - <completionHelp> - <path>policy prefix-list</path> - </completionHelp> </properties> - </leafNode> + <children> + #include <include/policy/prefix-list.xml.i> + </children> + </node> <node name="rp"> <properties> <help>Rendezvous Point</help> </properties> <children> <tagNode name="address"> <properties> <help>Rendezvous Point address</help> <valueHelp> <format>ipv4</format> <description>Rendezvous Point address</description> </valueHelp> <constraint> <validator name="ipv4-address"/> </constraint> </properties> <children> <leafNode name="group"> <properties> <help>Group Address range</help> <valueHelp> <format>ipv4net</format> <description>Group Address range RFC 3171</description> </valueHelp> <constraint> <validator name="ip-prefix"/> </constraint> <multi/> </properties> </leafNode> </children> </tagNode> - <leafNode name="send-v6-secondary"> - <properties> - <help>Send v6 secondary addresses</help> - <valueless/> - </properties> - </leafNode> - <node name="spt-switchover"> + #include <include/pim/keep-alive-timer.xml.i> + </children> + </node> + <leafNode name="no-v6-secondary"> + <properties> + <help>Disable IPv6 secondary address in hello packets</help> + <valueless/> + </properties> + </leafNode> + <node name="spt-switchover"> + <properties> + <help>Send v6 secondary addresses</help> + </properties> + <children> + <node name="infinity-and-beyond"> <properties> - <help>Send v6 secondary addresses</help> + <help>Never switch to SPT Tree</help> </properties> <children> - <node name="infinity-and-beyond"> - <properties> - <help>Never switch to SPT Tree</help> - </properties> - <children> - <leafNode name="prefix-list"> - <properties> - <help>Prefix-List to control which groups to switch</help> - <valueHelp> - <format>txt</format> - <description>Prefix-list to apply</description> - </valueHelp> - <completionHelp> - <path>policy prefix-list</path> - </completionHelp> - </properties> - </leafNode> - </children> - </node> + #include <include/policy/prefix-list.xml.i> </children> </node> - <leafNode name="ssm"> - <properties> - <help>Source-Specific Multicast</help> - <completionHelp> - <path>policy prefix-list</path> - </completionHelp> - <valueHelp> - <format>txt</format> - <description>Prefix-list to apply</description> - </valueHelp> - </properties> - </leafNode> + </children> + </node> + <node name="ssm"> + <properties> + <help>Source-Specific Multicast</help> + </properties> + <children> + #include <include/policy/prefix-list.xml.i> </children> </node> </children> </node> </children> </node> </interfaceDefinition> diff --git a/op-mode-definitions/show-ip-pim.xml.in b/op-mode-definitions/show-ip-pim.xml.in index 3e0bff064..9deba1f07 100644 --- a/op-mode-definitions/show-ip-pim.xml.in +++ b/op-mode-definitions/show-ip-pim.xml.in @@ -1,156 +1,156 @@ <?xml version="1.0"?> <interfaceDefinition> <node name="show"> <children> <node name="ip"> <children> <node name="pim"> <properties> <help>Show PIM (Protocol Independent Multicast) information</help> </properties> <children> <leafNode name="assert"> <properties> <help>PIM interfaces assert</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="assert-internal"> <properties> <help>PIM interface internal assert state</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="assert-metric"> <properties> <help>PIM interface assert metric</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="assert-winner-metric"> <properties> <help>PIM interface assert winner metric</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="bsm-database"> <properties> <help>PIM cached bsm packets information</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="bsr"> <properties> <help>PIM boot-strap router information</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="bsrp-info"> <properties> <help>PIM cached group-rp mappings information</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="channel"> <properties> <help>PIM downstream channel info</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="group-type"> <properties> <help>PIM multicast group type</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> - <leafNode name="interfaces"> + <leafNode name="interface"> <properties> <help>PIM interfaces information</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="join"> <properties> <help>PIM join information</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="local-membership"> <properties> <help>PIM interface local-membership</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="neighbor"> <properties> <help>PIM neighbor information</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="nexthop"> <properties> <help>PIM cached nexthop rpf information</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="rp-info"> <properties> <help>PIM rendezvous point information</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="rpf"> <properties> <help>PIM reverse path forwarding information</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="secondary"> <properties> <help>PIM neighbor addresses</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="state"> <properties> <help>PIM state information</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="statistics"> <properties> <help>PIM statistics</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="upstream"> <properties> <help>PIM upstream information</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="upstream-join-desired"> <properties> <help>PIM upstream join-desired</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="upstream-rpf"> <properties> <help>PIM upstream source reverse path forwarding</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="vxlan-groups"> <properties> <help>VXLAN BUM groups</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> </children> </node> </children> </node> </children> </node> </interfaceDefinition> diff --git a/smoketest/scripts/cli/test_protocols_pim.py b/smoketest/scripts/cli/test_protocols_pim.py index ef134b195..ccfced138 100755 --- a/smoketest/scripts/cli/test_protocols_pim.py +++ b/smoketest/scripts/cli/test_protocols_pim.py @@ -1,144 +1,192 @@ #!/usr/bin/env python3 # # 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 # 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 unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section from vyos.utils.process import process_named_running PROCESS_NAME = 'pimd' base_path = ['protocols', 'pim'] class TestProtocolsPIM(VyOSUnitTestSHIM.TestCase): def tearDown(self): # pimd process must be running self.assertTrue(process_named_running(PROCESS_NAME)) self.cli_delete(base_path) self.cli_commit() # pimd process must be stopped by now self.assertFalse(process_named_running(PROCESS_NAME)) def test_01_pim_basic(self): rp = '127.0.0.1' group = '224.0.0.0/4' hello = '100' dr_priority = '64' self.cli_set(base_path + ['rp', 'address', rp, 'group', group]) interfaces = Section.interfaces('ethernet') for interface in interfaces: self.cli_set(base_path + ['interface', interface , 'bfd']) self.cli_set(base_path + ['interface', interface , 'dr-priority', dr_priority]) self.cli_set(base_path + ['interface', interface , 'hello', hello]) self.cli_set(base_path + ['interface', interface , 'no-bsm']) self.cli_set(base_path + ['interface', interface , 'no-unicast-bsm']) self.cli_set(base_path + ['interface', interface , 'passive']) # commit changes self.cli_commit() # Verify FRR pimd configuration frrconfig = self.getFRRconfig(daemon=PROCESS_NAME) self.assertIn(f'ip pim rp {rp} {group}', frrconfig) for interface in interfaces: frrconfig = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) self.assertIn(f'interface {interface}', frrconfig) self.assertIn(f' ip pim', frrconfig) self.assertIn(f' ip pim bfd', frrconfig) self.assertIn(f' ip pim drpriority {dr_priority}', frrconfig) self.assertIn(f' ip pim hello {hello}', frrconfig) self.assertIn(f' no ip pim bsm', frrconfig) self.assertIn(f' no ip pim unicast-bsm', frrconfig) self.assertIn(f' ip pim passive', frrconfig) self.cli_commit() - def test_02_pim_igmp_proxy(self): + def test_02_pim_advanced(self): + rp = '127.0.0.2' + group = '224.0.0.0/4' + join_prune_interval = '123' + rp_keep_alive_timer = '190' + keep_alive_timer = '180' + packets = '10' + prefix_list = 'pim-test' + register_suppress_time = '300' + + self.cli_set(base_path + ['rp', 'address', rp, 'group', group]) + self.cli_set(base_path + ['rp', 'keep-alive-timer', rp_keep_alive_timer]) + + self.cli_set(base_path + ['ecmp', 'rebalance']) + self.cli_set(base_path + ['join-prune-interval', join_prune_interval]) + self.cli_set(base_path + ['keep-alive-timer', keep_alive_timer]) + self.cli_set(base_path + ['packets', packets]) + self.cli_set(base_path + ['register-accept-list', 'prefix-list', prefix_list]) + self.cli_set(base_path + ['register-suppress-time', register_suppress_time]) + self.cli_set(base_path + ['no-v6-secondary']) + self.cli_set(base_path + ['spt-switchover', 'infinity-and-beyond', 'prefix-list', prefix_list]) + self.cli_set(base_path + ['ssm', 'prefix-list', prefix_list]) + + # check validate() - PIM require defined interfaces! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + interfaces = Section.interfaces('ethernet') + for interface in interfaces: + self.cli_set(base_path + ['interface', interface]) + + # commit changes + self.cli_commit() + + # Verify FRR pimd configuration + frrconfig = self.getFRRconfig(daemon=PROCESS_NAME) + self.assertIn(f'ip pim rp {rp} {group}', frrconfig) + self.assertIn(f'ip pim rp keep-alive-timer {rp_keep_alive_timer}', frrconfig) + self.assertIn(f'ip pim ecmp rebalance', frrconfig) + self.assertIn(f'ip pim join-prune-interval {join_prune_interval}', frrconfig) + self.assertIn(f'ip pim keep-alive-timer {keep_alive_timer}', frrconfig) + self.assertIn(f'ip pim packets {packets}', frrconfig) + self.assertIn(f'ip pim register-accept-list {prefix_list}', frrconfig) + self.assertIn(f'ip pim register-suppress-time {register_suppress_time}', frrconfig) + self.assertIn(f'no ip pim send-v6-secondary', frrconfig) + self.assertIn(f'ip pim spt-switchover infinity-and-beyond prefix-list {prefix_list}', frrconfig) + self.assertIn(f'ip pim ssm prefix-list {prefix_list}', frrconfig) + + def test_03_pim_igmp_proxy(self): igmp_proxy = ['protocols', 'igmp-proxy'] rp = '127.0.0.1' group = '224.0.0.0/4' self.cli_set(base_path) self.cli_set(igmp_proxy) # check validate() - can not set both IGMP proxy and PIM with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_delete(igmp_proxy) self.cli_set(base_path + ['rp', 'address', rp, 'group', group]) interfaces = Section.interfaces('ethernet') for interface in interfaces: self.cli_set(base_path + ['interface', interface , 'bfd']) # commit changes self.cli_commit() - def test_03_igmp(self): + def test_04_igmp(self): watermark_warning = '2000' query_interval = '1000' query_max_response_time = '200' version = '2' igmp_join = { '224.1.1.1' : { 'source' : ['1.1.1.1', '2.2.2.2', '3.3.3.3'] }, '224.1.2.2' : { 'source' : [] }, '224.1.3.3' : {}, } self.cli_set(base_path + ['igmp', 'watermark-warning', watermark_warning]) interfaces = Section.interfaces('ethernet') for interface in interfaces: self.cli_set(base_path + ['interface', interface , 'igmp', 'version', version]) self.cli_set(base_path + ['interface', interface , 'igmp', 'query-interval', query_interval]) self.cli_set(base_path + ['interface', interface , 'igmp', 'query-max-response-time', query_max_response_time]) for join, join_config in igmp_join.items(): self.cli_set(base_path + ['interface', interface , 'igmp', 'join', join]) if 'source' in join_config: for source in join_config['source']: self.cli_set(base_path + ['interface', interface , 'igmp', 'join', join, 'source-address', source]) self.cli_commit() frrconfig = self.getFRRconfig(daemon=PROCESS_NAME) self.assertIn(f'ip igmp watermark-warn {watermark_warning}', frrconfig) for interface in interfaces: frrconfig = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) self.assertIn(f'interface {interface}', frrconfig) self.assertIn(f' ip igmp', frrconfig) self.assertIn(f' ip igmp version {version}', frrconfig) self.assertIn(f' ip igmp query-interval {query_interval}', frrconfig) self.assertIn(f' ip igmp query-max-response-time {query_max_response_time}', frrconfig) for join, join_config in igmp_join.items(): if 'source' in join_config: for source in join_config['source']: self.assertIn(f' ip igmp join {join} {source}', frrconfig) else: self.assertIn(f' ip igmp join {join}', frrconfig) if __name__ == '__main__': - unittest.main(verbosity=2, failfast=True) + unittest.main(verbosity=2) diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py index fbe95c404..5e6225f6f 100755 --- a/src/conf_mode/protocols_pim.py +++ b/src/conf_mode/protocols_pim.py @@ -1,160 +1,164 @@ #!/usr/bin/env python3 # # Copyright (C) 2020-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 os from ipaddress import IPv4Network from signal import SIGTERM from sys import exit from vyos.config import Config from vyos.config import config_dict_merge from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.utils.dict import dict_search +from vyos.configverify import verify_interface_exists from vyos.utils.process import process_named_running from vyos.utils.process import call from vyos.template import render_to_string from vyos import ConfigError from vyos import frr from vyos import airbag airbag.enable() def get_config(config=None): if config: conf = config else: conf = Config() base = ['protocols', 'pim'] pim = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) # We can not run both IGMP proxy and PIM at the same time - get IGMP # proxy status if conf.exists(['protocols', 'igmp-proxy']): pim.update({'igmp_proxy_enabled' : {}}) # FRR has VRF support for different routing daemons. As interfaces belong # to VRFs - or the global VRF, we need to check for changed interfaces so # that they will be properly rendered for the FRR config. Also this eases # removal of interfaces from the running configuration. interfaces_removed = node_changed(conf, base + ['interface']) if interfaces_removed: pim['interface_removed'] = list(interfaces_removed) # Bail out early if configuration tree does no longer exist. this must # be done after retrieving the list of interfaces to be removed. if not conf.exists(base): pim.update({'deleted' : ''}) return pim # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. default_values = conf.get_config_defaults(**pim.kwargs, recursive=True) # We have to cleanup the default dict, as default values could enable features # which are not explicitly enabled on the CLI. Example: default-information # originate comes with a default metric-type of 2, which will enable the # entire default-information originate tree, even when not set via CLI so we # need to check this first and probably drop that key. for interface in pim.get('interface', []): # We need to reload the defaults on every pass b/c of # hello-multiplier dependency on dead-interval # If hello-multiplier is set, we need to remove the default from # dead-interval. if 'igmp' not in pim['interface'][interface]: del default_values['interface'][interface]['igmp'] pim = config_dict_merge(default_values, pim) return pim def verify(pim): if not pim or 'deleted' in pim: return None if 'igmp_proxy_enabled' in pim: raise ConfigError('IGMP proxy and PIM cannot be configured at the same time!') if 'interface' not in pim: raise ConfigError('PIM require defined interfaces!') + for interface in pim['interface']: + verify_interface_exists(interface) + if 'rp' in pim: if 'address' not in pim['rp']: raise ConfigError('PIM rendezvous point needs to be defined!') # Check unique multicast groups unique = [] pim_base_error = 'PIM rendezvous point group' for address, address_config in pim['rp']['address'].items(): if 'group' not in address_config: raise ConfigError(f'{pim_base_error} should be defined for "{address}"!') # Check if it is a multicast group for gr_addr in address_config['group']: if not IPv4Network(gr_addr).is_multicast: raise ConfigError(f'{pim_base_error} "{gr_addr}" is not a multicast group!') if gr_addr in unique: raise ConfigError(f'{pim_base_error} must be unique!') unique.append(gr_addr) def generate(pim): if not pim or 'deleted' in pim: return None pim['frr_pimd_config'] = render_to_string('frr/pimd.frr.j2', pim) return None def apply(pim): pim_daemon = 'pimd' pim_pid = process_named_running(pim_daemon) if not pim or 'deleted' in pim: if 'deleted' in pim: os.kill(int(pim_pid), SIGTERM) return None if not pim_pid: call('/usr/lib/frr/pimd -d -F traditional --daemon -A 127.0.0.1') # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() frr_cfg.load_configuration(pim_daemon) frr_cfg.modify_section(f'^ip pim') frr_cfg.modify_section(f'^ip igmp') for key in ['interface', 'interface_removed']: if key not in pim: continue for interface in pim[key]: frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) if 'frr_pimd_config' in pim: frr_cfg.add_before(frr.default_add_before, pim['frr_pimd_config']) frr_cfg.commit_configuration(pim_daemon) return None if __name__ == '__main__': try: c = get_config() verify(c) generate(c) apply(c) except ConfigError as e: print(e) exit(1)