diff options
author | Christian Breunig <christian@breunig.cc> | 2023-11-12 18:30:15 +0100 |
---|---|---|
committer | Christian Breunig <christian@breunig.cc> | 2023-11-15 20:23:49 +0100 |
commit | 6b44aa0692653493b8d0e0c639a4369860ec603f (patch) | |
tree | 5f162f1b038dd5d7a5cba45b8d594f4075a76f2c | |
parent | 7b0eaba2d365e5bdf28d74e5b6cca629ff03c5da (diff) | |
download | vyos-1x-6b44aa0692653493b8d0e0c639a4369860ec603f.tar.gz vyos-1x-6b44aa0692653493b8d0e0c639a4369860ec603f.zip |
pim: T5733: add missing FRR PIM related features
Migrate CLI configuration retrival to common get_config_dict(). In addition
add new functionality to VyOS that is PIM related and already available in FRR.
(cherry picked from commit 9abc02edcc237760f1f8aa1b3f08d7f4d18f866c)
# Conflicts:
# python/vyos/frr.py
# src/op_mode/restart_frr.py
-rw-r--r-- | data/templates/frr/pimd.frr.j2 | 95 | ||||
-rw-r--r-- | interface-definitions/protocols-pim.xml.in | 188 | ||||
-rw-r--r-- | op-mode-definitions/show-ip-pim.xml.in | 114 | ||||
-rw-r--r-- | python/vyos/frr.py | 5 | ||||
-rw-r--r-- | smoketest/scripts/cli/base_interfaces_test.py | 4 | ||||
-rw-r--r-- | smoketest/scripts/cli/base_vyostest_shim.py | 5 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_protocols_pim.py | 101 | ||||
-rwxr-xr-x | src/conf_mode/protocols_pim.py | 203 | ||||
-rwxr-xr-x | src/op_mode/restart_frr.py | 2 |
9 files changed, 552 insertions, 165 deletions
diff --git a/data/templates/frr/pimd.frr.j2 b/data/templates/frr/pimd.frr.j2 index cb2f2aa98..4841efd15 100644 --- a/data/templates/frr/pimd.frr.j2 +++ b/data/templates/frr/pimd.frr.j2 @@ -1,34 +1,75 @@ +{% if interface is vyos_defined %} +{% for iface, iface_config in interface.items() %} ! -{% for rp_addr in old_pim.rp %} -{% for group in old_pim.rp[rp_addr] %} -no ip pim rp {{ rp_addr }} {{ group }} +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.version is vyos_defined %} + ip igmp version {{ iface_config.igmp.version }} +{% endif %} +exit {% endfor %} -{% endfor %} -{% if old_pim.rp_keep_alive %} -no ip pim rp keep-alive-timer {{ old_pim.rp_keep_alive }} {% endif %} -{% for iface in old_pim.ifaces %} -interface {{ iface }} -no ip pim -! -{% endfor %} -{% for iface in pim.ifaces %} -interface {{ iface }} -ip pim -{% if pim.ifaces[iface].dr_prio %} -ip pim drpriority {{ pim.ifaces[iface].dr_prio }} -{% endif %} -{% if pim.ifaces[iface].hello %} -ip pim hello {{ pim.ifaces[iface].hello }} -{% endif %} ! -{% endfor %} -{% for rp_addr in pim.rp %} -{% for group in pim.rp[rp_addr] %} -ip pim rp {{ rp_addr }} {{ group }} +{% 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 }} +{% 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 }} +{% 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 %} -{% endfor %} -{% if pim.rp_keep_alive %} -ip pim rp keep-alive-timer {{ pim.rp_keep_alive }} +{% endif %} +{% if send_v6_secondary is vyos_defined %} +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 }} +{% endif %} +! +{% if igmp.watermark_warn is vyos_defined %} +ip igmp watermark-warn {{ igmp.watermark_warn }} {% endif %} ! diff --git a/interface-definitions/protocols-pim.xml.in b/interface-definitions/protocols-pim.xml.in index e9475930c..733279aa4 100644 --- a/interface-definitions/protocols-pim.xml.in +++ b/interface-definitions/protocols-pim.xml.in @@ -15,8 +15,24 @@ <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> + <leafNode name="no-bsm"> + <properties> + <help>Do not process bootstrap messages</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="no-unicast-bsm"> + <properties> + <help>Do not process unicast bootstrap messages</help> + <valueless/> + </properties> + </leafNode> <leafNode name="dr-priority"> <properties> <help>Designated Router Election Priority</help> @@ -41,8 +57,134 @@ </constraint> </properties> </leafNode> + <node name="igmp"> + <properties> + <help>Internet Group Management Protocol (IGMP) options</help> + </properties> + <children> + <leafNode name="version"> + <properties> + <help>Interface IGMP version</help> + <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> + <leafNode name="passive"> + <properties> + <help>Disable sending and receiving PIM control packets on the interface</help> + <valueless/> + </properties> + </leafNode> + #include <include/source-address-ipv4.xml.i> </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-warn"> + <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> + <leafNode name="join-prune-interval"> + <properties> + <help>Join Prune Send Interval</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + <defaultValue>60</defaultValue> + </leafNode> + <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> + <leafNode name="packets"> + <properties> + <help>Packets to process at once</help> + <valueHelp> + <format>u32:1-255</format> + <description>Number of packets</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + <defaultValue>3</defaultValue> + </leafNode> + <leafNode 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> + <leafNode name="register-suppress-time"> + <properties> + <help>Register Suppress Timer</help> + <valueHelp> + <format>u32:1-65535</format> + <description>In seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> <node name="rp"> <properties> <help>Rendezvous Point</help> @@ -75,16 +217,48 @@ </leafNode> </children> </tagNode> - <leafNode name="keep-alive-timer"> + <leafNode name="send-v6-secondary"> <properties> - <help>Keep alive Timer</help> + <help>Send v6 secondary addresses</help> + <valueless/> + </properties> + </leafNode> + <node name="spt-switchover"> + <properties> + <help>Send v6 secondary addresses</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> + </children> + </node> + <leafNode name="ssm"> + <properties> + <help>Source-Specific Multicast</help> + <completionHelp> + <path>policy prefix-list</path> + </completionHelp> <valueHelp> - <format>u32:31-60000</format> - <description>Keep alive Timer in seconds</description> + <format>txt</format> + <description>Prefix-list to apply</description> </valueHelp> - <constraint> - <validator name="numeric" argument="--range 31-60000"/> - </constraint> </properties> </leafNode> </children> diff --git a/op-mode-definitions/show-ip-pim.xml.in b/op-mode-definitions/show-ip-pim.xml.in index fa317a944..3e0bff064 100644 --- a/op-mode-definitions/show-ip-pim.xml.in +++ b/op-mode-definitions/show-ip-pim.xml.in @@ -9,59 +9,143 @@ <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"> <properties> <help>PIM interfaces information</help> </properties> - <command>vtysh -c "show ip pim interface"</command> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="join"> <properties> <help>PIM join information</help> </properties> - <command>vtysh -c "show ip pim join"</command> + <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>vtysh -c "show ip pim neighbor"</command> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="nexthop"> <properties> <help>PIM cached nexthop rpf information</help> </properties> - <command>vtysh -c "show ip pim nexthop"</command> + <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>vtysh -c "show ip pim state"</command> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> <leafNode name="statistics"> <properties> <help>PIM statistics</help> </properties> - <command>vtysh -c "show ip pim statistics"</command> + <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="rp"> + <leafNode name="upstream-join-desired"> <properties> - <help>PIM RP (Rendevous Point) information</help> + <help>PIM upstream join-desired</help> </properties> - <command>vtysh -c "show ip pim rp-info"</command> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> - <leafNode name="rpf"> + <leafNode name="upstream-rpf"> <properties> - <help>PIM cached source rpf information</help> + <help>PIM upstream source reverse path forwarding</help> </properties> - <command>vtysh -c "show ip pim rpf"</command> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> - <leafNode name="upstream"> + <leafNode name="vxlan-groups"> <properties> - <help>PIM upstream information</help> + <help>VXLAN BUM groups</help> </properties> - <command>vtysh -c "show ip pim upstream"</command> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> </children> </node> diff --git a/python/vyos/frr.py b/python/vyos/frr.py index 2e3c8a271..a01d967e4 100644 --- a/python/vyos/frr.py +++ b/python/vyos/frr.py @@ -86,9 +86,8 @@ ch2 = logging.StreamHandler(stream=sys.stdout) LOG.addHandler(ch) LOG.addHandler(ch2) -_frr_daemons = ['zebra', 'bgpd', 'fabricd', 'isisd', 'ospf6d', 'ospfd', 'pbrd', - 'pimd', 'ripd', 'ripngd', 'sharpd', 'staticd', 'vrrpd', 'ldpd', - 'bfdd', 'eigrpd', 'babeld'] +_frr_daemons = ['zebra', 'staticd', 'bgpd', 'ospfd', 'ospf6d', 'ripd', 'ripngd', + 'isisd', 'pimd', 'pim6d', 'ldpd', 'eigrpd', 'babeld', 'bfdd'] path_vtysh = '/usr/bin/vtysh' path_frr_reload = '/usr/lib/frr/frr-reload.py' diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index 820024dc9..277f14067 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -12,9 +12,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 -import unittest - from binascii import hexlify from netifaces import AF_INET @@ -24,7 +21,6 @@ from netifaces import interfaces from base_vyostest_shim import VyOSUnitTestSHIM -from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError from vyos.defaults import directories from vyos.ifconfig import Interface diff --git a/smoketest/scripts/cli/base_vyostest_shim.py b/smoketest/scripts/cli/base_vyostest_shim.py index f694f539d..140642806 100644 --- a/smoketest/scripts/cli/base_vyostest_shim.py +++ b/smoketest/scripts/cli/base_vyostest_shim.py @@ -78,9 +78,10 @@ class VyOSUnitTestSHIM: while run(f'sudo lsof -nP {commit_lock}') == 0: sleep(0.250) - def getFRRconfig(self, string, end='$', endsection='^!', daemon=''): + def getFRRconfig(self, string=None, end='$', endsection='^!', daemon=''): """ Retrieve current "running configuration" from FRR """ - command = f'vtysh -c "show run {daemon} no-header" | sed -n "/^{string}{end}/,/{endsection}/p"' + command = f'vtysh -c "show run {daemon} no-header"' + if string: command += f' | sed -n "/^{string}{end}/,/{endsection}/p"' out = cmd(command) if self.debug: import pprint diff --git a/smoketest/scripts/cli/test_protocols_pim.py b/smoketest/scripts/cli/test_protocols_pim.py new file mode 100755 index 000000000..07c806126 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_pim.py @@ -0,0 +1,101 @@ +#!/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): + igmp_proxy = ['protocols', 'igmp-proxy'] + rp = '127.0.0.1' + group = '224.0.0.0/4' + hello = '100' + dr_priority = '64' + + 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() + +if __name__ == '__main__': + unittest.main(verbosity=2, failfast=True) diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py index 0aaa0d2c6..89db69b87 100755 --- a/src/conf_mode/protocols_pim.py +++ b/src/conf_mode/protocols_pim.py @@ -16,144 +16,135 @@ import os -from ipaddress import IPv4Address +from ipaddress import IPv4Network +from signal import SIGTERM from sys import exit from vyos.config import Config -from vyos import ConfigError +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.utils.process import process_named_running from vyos.utils.process import call -from vyos.template import render -from signal import SIGTERM - +from vyos.template import render_to_string +from vyos import ConfigError +from vyos import frr from vyos import airbag airbag.enable() -# Required to use the full path to pimd, in another case daemon will not be started -pimd_cmd = f'/usr/lib/frr/pimd -d -F traditional --daemon -A 127.0.0.1' - -config_file = r'/tmp/pimd.frr' - def get_config(config=None): if config: conf = config else: conf = Config() - pim_conf = { - 'pim_conf' : False, - 'igmp_conf' : False, - 'igmp_proxy_conf' : False, - 'old_pim' : { - 'ifaces' : {}, - 'rp' : {} - }, - 'pim' : { - 'ifaces' : {}, - 'rp' : {} - } - } - if not (conf.exists('protocols pim') or conf.exists_effective('protocols pim')): - return None - - if conf.exists('protocols igmp-proxy'): - pim_conf['igmp_proxy_conf'] = True - - if conf.exists('protocols igmp'): - pim_conf['igmp_conf'] = True - - if conf.exists('protocols pim'): - pim_conf['pim_conf'] = True - - conf.set_level('protocols pim') - # Get interfaces - for iface in conf.list_effective_nodes('interface'): - pim_conf['old_pim']['ifaces'].update({ - iface : { - 'hello' : conf.return_effective_value('interface {0} hello'.format(iface)), - 'dr_prio' : conf.return_effective_value('interface {0} dr-priority'.format(iface)) - } - }) - - for iface in conf.list_nodes('interface'): - pim_conf['pim']['ifaces'].update({ - iface : { - 'hello' : conf.return_value('interface {0} hello'.format(iface)), - 'dr_prio' : conf.return_value('interface {0} dr-priority'.format(iface)), - } - }) - - conf.set_level('protocols pim rp') - - # Get RPs addresses - for rp_addr in conf.list_effective_nodes('address'): - pim_conf['old_pim']['rp'][rp_addr] = conf.return_effective_values('address {0} group'.format(rp_addr)) - - for rp_addr in conf.list_nodes('address'): - pim_conf['pim']['rp'][rp_addr] = conf.return_values('address {0} group'.format(rp_addr)) - - # Get RP keep-alive-timer - if conf.exists_effective('rp keep-alive-timer'): - pim_conf['old_pim']['rp_keep_alive'] = conf.return_effective_value('rp keep-alive-timer') - if conf.exists('rp keep-alive-timer'): - pim_conf['pim']['rp_keep_alive'] = conf.return_value('rp keep-alive-timer') - - return pim_conf + 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 pim is None: + if not pim or 'deleted' in pim: return None - if pim['pim_conf']: - # Check conflict with IGMP-Proxy - if pim['igmp_proxy_conf']: - raise ConfigError(f"IGMP proxy and PIM cannot be both configured at the same time") + if 'igmp_proxy_enabled' in pim: + raise ConfigError('IGMP proxy and PIM cannot be configured at the same time!') - # Check interfaces - if not pim['pim']['ifaces']: - raise ConfigError(f"PIM require defined interfaces!") + if 'interface' not in pim: + raise ConfigError('PIM require defined interfaces!') - if not pim['pim']['rp']: - raise ConfigError(f"RP address required") + if dict_search('rp.address', pim) == None: + raise ConfigError('PIM rendezvous point needs to be defined!') - # Check unique multicast groups - uniq_groups = [] - for rp_addr in pim['pim']['rp']: - if not pim['pim']['rp'][rp_addr]: - raise ConfigError(f"Group should be specified for RP " + rp_addr) - for group in pim['pim']['rp'][rp_addr]: - if (group in uniq_groups): - raise ConfigError(f"Group range " + group + " specified cannot exact match another") + # Check unique multicast groups + unique = [] + for address, address_config in pim['rp']['address'].items(): + if 'group' not in address_config: + raise ConfigError(f'PIM rendezvous point group should be defined for "{address}"!') - # Check, is this multicast group - gr_addr = group.split('/') - if IPv4Address(gr_addr[0]) < IPv4Address('224.0.0.0'): - raise ConfigError(group + " not a multicast group") - - uniq_groups.extend(pim['pim']['rp'][rp_addr]) + # 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 rendezvous point group "{gr_addr}" is not a multicast group!') + if gr_addr in unique: + raise ConfigError('PIM rendezvous point group must be unique!') + unique.append(gr_addr) def generate(pim): - if pim is None: + if not pim or 'deleted' in pim: return None - - render(config_file, 'frr/pimd.frr.j2', pim) + pim['frr_pimd_config'] = render_to_string('frr/pimd.frr.j2', pim) return None def apply(pim): - if pim is None: + 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 - pim_pid = process_named_running('pimd') - if pim['igmp_conf'] or pim['pim_conf']: - if not pim_pid: - call(pimd_cmd) + 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') - if os.path.exists(config_file): - call("vtysh -d pimd -f " + config_file) - os.remove(config_file) - elif pim_pid: - os.kill(int(pim_pid), SIGTERM) + 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__': diff --git a/src/op_mode/restart_frr.py b/src/op_mode/restart_frr.py index 5cce377eb..8841b0eca 100755 --- a/src/op_mode/restart_frr.py +++ b/src/op_mode/restart_frr.py @@ -139,7 +139,7 @@ def _reload_config(daemon): # define program arguments cmd_args_parser = argparse.ArgumentParser(description='restart frr daemons') cmd_args_parser.add_argument('--action', choices=['restart'], required=True, help='action to frr daemons') -cmd_args_parser.add_argument('--daemon', choices=['bfdd', 'bgpd', 'ldpd', 'ospfd', 'ospf6d', 'isisd', 'ripd', 'ripngd', 'staticd', 'zebra', 'babeld'], required=False, nargs='*', help='select single or multiple daemons') +cmd_args_parser.add_argument('--daemon', choices=['zebra', 'staticd', 'bgpd', 'eigrpd', 'ospfd', 'ospf6d', 'ripd', 'ripngd', 'isisd', 'pimd', 'pim6d', 'ldpd', 'babeld', 'bfdd'], required=False, nargs='*', help='select single or multiple daemons') # parse arguments cmd_args = cmd_args_parser.parse_args() |