summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2023-11-12 18:30:15 +0100
committerChristian Breunig <christian@breunig.cc>2023-11-15 20:23:49 +0100
commit6b44aa0692653493b8d0e0c639a4369860ec603f (patch)
tree5f162f1b038dd5d7a5cba45b8d594f4075a76f2c
parent7b0eaba2d365e5bdf28d74e5b6cca629ff03c5da (diff)
downloadvyos-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.j295
-rw-r--r--interface-definitions/protocols-pim.xml.in188
-rw-r--r--op-mode-definitions/show-ip-pim.xml.in114
-rw-r--r--python/vyos/frr.py5
-rw-r--r--smoketest/scripts/cli/base_interfaces_test.py4
-rw-r--r--smoketest/scripts/cli/base_vyostest_shim.py5
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_pim.py101
-rwxr-xr-xsrc/conf_mode/protocols_pim.py203
-rwxr-xr-xsrc/op_mode/restart_frr.py2
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()