summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2023-11-13 22:19:44 +0100
committerChristian Breunig <christian@breunig.cc>2023-11-15 20:23:49 +0100
commit101a0f0f003b30d6023ad79e4a827aa67abf26d7 (patch)
tree2d6076c01c53aa0a82e9f99e0a7b3e3abc1b45f7
parentc139f40e51c855a701e307067809fc91e21cdd65 (diff)
downloadvyos-1x-101a0f0f003b30d6023ad79e4a827aa67abf26d7.tar.gz
vyos-1x-101a0f0f003b30d6023ad79e4a827aa67abf26d7.zip
pim6: T5733: add missing FRR PIM6 related features
(cherry picked from commit 403d2ffd6e46cb082b1d16ddf515e1784bee968c) # Conflicts: # data/templates/frr/pim6d.frr.j2 # interface-definitions/protocols-pim6.xml.in # smoketest/scripts/cli/test_protocols_pim6.py # src/conf_mode/protocols_pim6.py
-rw-r--r--data/templates/frr/pim6d.frr.j281
-rw-r--r--interface-definitions/include/policy/prefix-list6.xml.i14
-rw-r--r--interface-definitions/protocols-pim6.xml.in179
-rw-r--r--op-mode-definitions/show-ipv6-mld.xml.in42
-rw-r--r--op-mode-definitions/show-ipv6-pim.xml.in120
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_pim6.py139
-rwxr-xr-xsrc/conf_mode/protocols_pim6.py133
7 files changed, 708 insertions, 0 deletions
diff --git a/data/templates/frr/pim6d.frr.j2 b/data/templates/frr/pim6d.frr.j2
new file mode 100644
index 000000000..bac716fcc
--- /dev/null
+++ b/data/templates/frr/pim6d.frr.j2
@@ -0,0 +1,81 @@
+!
+{% if interface is vyos_defined %}
+{% for iface, iface_config in interface.items() %}
+!
+interface {{ iface }}
+ ipv6 pim
+{% if iface_config.no_bsm is vyos_defined %}
+ no ipv6 pim bsm
+{% endif %}
+{% if iface_config.dr_priority is vyos_defined %}
+ ipv6 pim drpriority {{ iface_config.dr_priority }}
+{% endif %}
+{% if iface_config.hello is vyos_defined %}
+ ipv6 pim hello {{ iface_config.hello }}
+{% endif %}
+{% if iface_config.no_unicast_bsm is vyos_defined %}
+ no ipv6 pim unicast-bsm
+{% endif %}
+{% if iface_config.passive is vyos_defined %}
+ ipv6 pim passive
+{% endif %}
+{% if iface_config.mld is vyos_defined and iface_config.mld.disable is not vyos_defined %}
+ ipv6 mld
+{% if iface_config.mld.version is vyos_defined %}
+ ipv6 mld version {{ iface_config.mld.version }}
+{% endif %}
+{% if iface_config.mld.interval is vyos_defined %}
+ ipv6 mld query-interval {{ iface_config.mld.interval }}
+{% endif %}
+{% if iface_config.mld.max_response_time is vyos_defined %}
+ ipv6 mld query-max-response-time {{ iface_config.mld.max_response_time // 100 }}
+{% endif %}
+{% if iface_config.mld.last_member_query_count is vyos_defined %}
+ ipv6 mld last-member-query-count {{ iface_config.mld.last_member_query_count }}
+{% endif %}
+{% if iface_config.mld.last_member_query_interval is vyos_defined %}
+ ipv6 mld last-member-query-interval {{ iface_config.mld.last_member_query_interval // 100 }}
+{% endif %}
+{% if iface_config.mld.join is vyos_defined %}
+{% for group, group_config in iface_config.mld.join.items() %}
+{% if group_config.source is vyos_defined %}
+{% for source in group_config.source %}
+ ipv6 mld join {{ group }} {{ source }}
+{% endfor %}
+{% else %}
+ ipv6 mld join {{ group }}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% endif %}
+exit
+{% endfor %}
+{% endif %}
+!
+{% if join_prune_interval is vyos_defined %}
+ipv6 pim join-prune-interval {{ join_prune_interval }}
+{% endif %}
+{% if keep_alive_timer is vyos_defined %}
+ipv6 pim keep-alive-timer {{ keep_alive_timer }}
+{% endif %}
+{% if packets is vyos_defined %}
+ipv6 pim packets {{ packets }}
+{% endif %}
+{% if register_suppress_time is vyos_defined %}
+ipv6 pim register-suppress-time {{ register_suppress_time }}
+{% endif %}
+{% if rp.address is vyos_defined %}
+{% for address, address_config in rp.address.items() %}
+{% if address_config.group is vyos_defined %}
+{% for group in address_config.group %}
+ipv6 pim rp {{ address }} {{ group }}
+{% endfor %}
+{% endif %}
+{% if address_config.prefix_list6 is vyos_defined %}
+ipv6 pim rp {{ address }} prefix-list {{ address_config.prefix_list6 }}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% if rp.keep_alive_timer is vyos_defined %}
+ipv6 pim rp keep-alive-timer {{ rp.keep_alive_timer }}
+{% endif %}
diff --git a/interface-definitions/include/policy/prefix-list6.xml.i b/interface-definitions/include/policy/prefix-list6.xml.i
new file mode 100644
index 000000000..101702f1f
--- /dev/null
+++ b/interface-definitions/include/policy/prefix-list6.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from policy/prefix-list6.xml.i -->
+<leafNode name="prefix-list6">
+ <properties>
+ <help>Prefix-list to use</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Prefix-list to apply (IPv6)</description>
+ </valueHelp>
+ <completionHelp>
+ <path>policy prefix-list6</path>
+ </completionHelp>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/protocols-pim6.xml.in b/interface-definitions/protocols-pim6.xml.in
new file mode 100644
index 000000000..8bd3f3fee
--- /dev/null
+++ b/interface-definitions/protocols-pim6.xml.in
@@ -0,0 +1,179 @@
+<?xml version="1.0"?>
+<!-- Protocol Independent Multicast for IPv6 (PIMv6) configuration -->
+<interfaceDefinition>
+ <node name="protocols">
+ <children>
+ <node name="pim6" owner="${vyos_conf_scripts_dir}/protocols_pim6.py">
+ <properties>
+ <help>Protocol Independent Multicast for IPv6 (PIMv6) and MLD</help>
+ <priority>400</priority>
+ </properties>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>PIMv6 interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ <constraint>
+ #include <include/constraint/interface-name.xml.i>
+ </constraint>
+ </properties>
+ <children>
+ #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>
+ <node name="mld">
+ <properties>
+ <help>Multicast Listener Discovery (MLD)</help>
+ </properties>
+ <children>
+ #include <include/generic-disable-node.xml.i>
+ <tagNode name="join">
+ <properties>
+ <help>MLD join multicast group</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Multicast group address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="source">
+ <properties>
+ <help>Source address</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Source address</description>
+ </valueHelp>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_local_ips.sh --ipv6</script>
+ </completionHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="last-member-query-count">
+ <properties>
+ <help>Last member query count</help>
+ <valueHelp>
+ <format>u32:1-255</format>
+ <description>Count</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="last-member-query-interval">
+ <properties>
+ <help>Last member query interval</help>
+ <valueHelp>
+ <format>u32:100-6553500</format>
+ <description>Last member query interval in milliseconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 100-6553500"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="interval">
+ <properties>
+ <help>Query interval</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Query interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="max-response-time">
+ <properties>
+ <help>Max query response time</help>
+ <valueHelp>
+ <format>u32:100-6553500</format>
+ <description>Query response value in milliseconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 100-6553500"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="version">
+ <properties>
+ <help>MLD version</help>
+ <completionHelp>
+ <list>1 2</list>
+ </completionHelp>
+ <valueHelp>
+ <format>1</format>
+ <description>MLD version 1</description>
+ </valueHelp>
+ <valueHelp>
+ <format>2</format>
+ <description>MLD version 2</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-2"/>
+ </constraint>
+ </properties>
+ <defaultValue>2</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ #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>
+ <node name="rp">
+ <properties>
+ <help>Rendezvous Point</help>
+ </properties>
+ <children>
+ <tagNode name="address">
+ <properties>
+ <help>Rendezvous Point address</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Rendezvous Point address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="group">
+ <properties>
+ <help>Group Address range</help>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>Group Address range</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ #include <include/policy/prefix-list6.xml.i>
+ </children>
+ </tagNode>
+ #include <include/pim/keep-alive-timer.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/show-ipv6-mld.xml.in b/op-mode-definitions/show-ipv6-mld.xml.in
new file mode 100644
index 000000000..5c719f700
--- /dev/null
+++ b/op-mode-definitions/show-ipv6-mld.xml.in
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="ipv6">
+ <children>
+ <node name="mld">
+ <properties>
+ <help>Show MLD (Multicast Listener Discovery) information</help>
+ </properties>
+ <children>
+ <leafNode name="groups">
+ <properties>
+ <help>MLD group information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
+ <leafNode name="interface">
+ <properties>
+ <help>MLD interface information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
+ <leafNode name="joins">
+ <properties>
+ <help>MLD joined groups and sources</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
+ <leafNode name="statistics">
+ <properties>
+ <help>MLD statistics</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/show-ipv6-pim.xml.in b/op-mode-definitions/show-ipv6-pim.xml.in
new file mode 100644
index 000000000..7cc3ce742
--- /dev/null
+++ b/op-mode-definitions/show-ipv6-pim.xml.in
@@ -0,0 +1,120 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="ipv6">
+ <children>
+ <node name="pim">
+ <properties>
+ <help>Show PIM (Protocol Independent Multicast) information</help>
+ </properties>
+ <children>
+ <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="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>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/smoketest/scripts/cli/test_protocols_pim6.py b/smoketest/scripts/cli/test_protocols_pim6.py
new file mode 100755
index 000000000..e22a7c722
--- /dev/null
+++ b/smoketest/scripts/cli/test_protocols_pim6.py
@@ -0,0 +1,139 @@
+#!/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 = 'pim6d'
+base_path = ['protocols', 'pim6']
+
+class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase):
+ def tearDown(self):
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+ def test_pim6_01_mld_simple(self):
+ # commit changes
+ interfaces = Section.interfaces('ethernet')
+ for interface in interfaces:
+ self.cli_set(base_path + ['interface', interface])
+
+ self.cli_commit()
+
+ # Verify FRR pim6d configuration
+ for interface in interfaces:
+ config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ self.assertIn(f'interface {interface}', config)
+ self.assertIn(f' ipv6 mld', config)
+ self.assertNotIn(f' ipv6 mld version 1', config)
+
+ # Change to MLD version 1
+ for interface in interfaces:
+ self.cli_set(base_path + ['interface', interface, 'mld', 'version', '1'])
+
+ self.cli_commit()
+
+ # Verify FRR pim6d configuration
+ for interface in interfaces:
+ config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ self.assertIn(f'interface {interface}', config)
+ self.assertIn(f' ipv6 mld', config)
+ self.assertIn(f' ipv6 mld version 1', config)
+
+ def test_pim6_02_mld_join(self):
+ interfaces = Section.interfaces('ethernet')
+ # Use an invalid multicast group address
+ for interface in interfaces:
+ self.cli_set(base_path + ['interface', interface, 'mld', 'join', 'fd00::1234'])
+
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(base_path + ['interface'])
+
+ # Use a valid multicast group address
+ for interface in interfaces:
+ self.cli_set(base_path + ['interface', interface, 'mld', 'join', 'ff18::1234'])
+
+ self.cli_commit()
+
+ # Verify FRR pim6d configuration
+ for interface in interfaces:
+ config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ self.assertIn(f'interface {interface}', config)
+ self.assertIn(f' ipv6 mld join ff18::1234', config)
+
+ # Join a source-specific multicast group
+ for interface in interfaces:
+ self.cli_set(base_path + ['interface', interface, 'mld', 'join', 'ff38::5678', 'source', '2001:db8::5678'])
+
+ self.cli_commit()
+
+ # Verify FRR pim6d configuration
+ for interface in interfaces:
+ config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ self.assertIn(f'interface {interface}', config)
+ self.assertIn(f' ipv6 mld join ff38::5678 2001:db8::5678', config)
+
+ def test_pim6_03_basic(self):
+ interfaces = Section.interfaces('ethernet')
+ join_prune_interval = '123'
+ keep_alive_timer = '77'
+ packets = '5'
+ register_suppress_time = '99'
+ dr_priority = '100'
+ hello = '50'
+
+ 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-suppress-time', register_suppress_time])
+
+ for interface in interfaces:
+ 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'])
+
+ self.cli_commit()
+
+ # Verify FRR pim6d configuration
+ config = self.getFRRconfig(daemon=PROCESS_NAME)
+ self.assertIn(f'ipv6 pim join-prune-interval {join_prune_interval}', config)
+ self.assertIn(f'ipv6 pim keep-alive-timer {keep_alive_timer}', config)
+ self.assertIn(f'ipv6 pim packets {packets}', config)
+ self.assertIn(f'ipv6 pim register-suppress-time {register_suppress_time}', config)
+
+ for interface in interfaces:
+ config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ self.assertIn(f' ipv6 pim drpriority {dr_priority}', config)
+ self.assertIn(f' ipv6 pim hello {hello}', config)
+ self.assertIn(f' no ipv6 pim bsm', config)
+ self.assertIn(f' no ipv6 pim unicast-bsm', config)
+ self.assertIn(f' ipv6 pim passive', config)
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/src/conf_mode/protocols_pim6.py b/src/conf_mode/protocols_pim6.py
new file mode 100755
index 000000000..2003a1014
--- /dev/null
+++ b/src/conf_mode/protocols_pim6.py
@@ -0,0 +1,133 @@
+#!/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/>.
+
+from ipaddress import IPv6Address
+from ipaddress import IPv6Network
+from sys import exit
+
+from vyos.config import Config
+from vyos.config import config_dict_merge
+from vyos.configdict import node_changed
+from vyos.configverify import verify_interface_exists
+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', 'pim6']
+ pim6 = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True, with_recursive_defaults=True)
+
+ # 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:
+ pim6['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):
+ pim6.update({'deleted' : ''})
+ return pim6
+
+ # 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(**pim6.kwargs, recursive=True)
+
+ pim6 = config_dict_merge(default_values, pim6)
+ return pim6
+
+def verify(pim6):
+ if not pim6 or 'deleted' in pim6:
+ return
+
+ for interface, interface_config in pim6.get('interface', {}).items():
+ verify_interface_exists(interface)
+ if 'mld' in interface_config:
+ mld = interface_config['mld']
+ for group in mld.get('join', {}).keys():
+ # Validate multicast group address
+ if not IPv6Address(group).is_multicast:
+ raise ConfigError(f"{group} is not a multicast group")
+
+ if 'rp' in pim6:
+ if 'address' not in pim6['rp']:
+ raise ConfigError('PIM6 rendezvous point needs to be defined!')
+
+ # Check unique multicast groups
+ unique = []
+ pim_base_error = 'PIM6 rendezvous point group'
+
+ if {'address', 'prefix-list6'} <= set(pim6['rp']):
+ raise ConfigError(f'{pim_base_error} supports either address or a prefix-list!')
+
+ for address, address_config in pim6['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 IPv6Network(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(pim6):
+ if not pim6 or 'deleted' in pim6:
+ return
+ pim6['new_frr_config'] = render_to_string('frr/pim6d.frr.j2', pim6)
+ return None
+
+def apply(pim6):
+ if pim6 is None:
+ return
+
+ pim6_daemon = 'pim6d'
+
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+
+ frr_cfg.load_configuration(pim6_daemon)
+
+ for key in ['interface', 'interface_removed']:
+ if key not in pim6:
+ continue
+ for interface in pim6[key]:
+ frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True)
+
+ if 'new_frr_config' in pim6:
+ frr_cfg.add_before(frr.default_add_before, pim6['new_frr_config'])
+ frr_cfg.commit_configuration(pim6_daemon)
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)