From dd13213ae94f071bc30cc17f5fabef02fbf95939 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Mon, 13 Nov 2023 21:36:31 +0100 Subject: pim: T5733: fix CLI level of global PIM commands --- interface-definitions/include/policy/prefix-list.xml.i | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 interface-definitions/include/policy/prefix-list.xml.i (limited to 'interface-definitions/include/policy') 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 @@ + + + + Prefix-list to use + + txt + Prefix-list to apply (IPv4) + + + policy prefix-list + + + + -- cgit v1.2.3 From 403d2ffd6e46cb082b1d16ddf515e1784bee968c Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Mon, 13 Nov 2023 22:19:44 +0100 Subject: pim6: T5733: add missing FRR PIM6 related features --- data/templates/frr/pim6d.frr.j2 | 45 +++++++- .../include/policy/prefix-list6.xml.i | 14 +++ interface-definitions/protocols-pim6.xml.in | 97 ++++++++++++----- op-mode-definitions/show-ipv6-mld.xml.in | 42 ++++++++ op-mode-definitions/show-ipv6-pim.xml.in | 120 +++++++++++++++++++++ smoketest/scripts/cli/test_protocols_pim6.py | 73 +++++++++---- src/conf_mode/protocols_pim6.py | 57 +++++++--- 7 files changed, 387 insertions(+), 61 deletions(-) create mode 100644 interface-definitions/include/policy/prefix-list6.xml.i create mode 100644 op-mode-definitions/show-ipv6-mld.xml.in create mode 100644 op-mode-definitions/show-ipv6-pim.xml.in (limited to 'interface-definitions/include/policy') diff --git a/data/templates/frr/pim6d.frr.j2 b/data/templates/frr/pim6d.frr.j2 index 8e430541d..bac716fcc 100644 --- a/data/templates/frr/pim6d.frr.j2 +++ b/data/templates/frr/pim6d.frr.j2 @@ -1,7 +1,24 @@ ! {% 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 %} @@ -32,7 +49,33 @@ interface {{ iface }} {% 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 @@ + + + + Prefix-list to use + + txt + Prefix-list to apply (IPv6) + + + policy prefix-list6 + + + + diff --git a/interface-definitions/protocols-pim6.xml.in b/interface-definitions/protocols-pim6.xml.in index 58ef5a1e3..8bd3f3fee 100644 --- a/interface-definitions/protocols-pim6.xml.in +++ b/interface-definitions/protocols-pim6.xml.in @@ -5,7 +5,7 @@ - Protocol Independent Multicast for IPv6 (PIMv6) + Protocol Independent Multicast for IPv6 (PIMv6) and MLD 400 @@ -15,8 +15,15 @@ + + #include + + #include + #include + #include + #include Multicast Listener Discovery (MLD) @@ -53,25 +60,29 @@ - + - MLD version - - 1 2 - + Last member query count - 1 - MLD version 1 + u32:1-255 + Count - - 2 - MLD version 2 + + + + + + + + Last member query interval + + u32:100-6553500 + Last member query interval in milliseconds - + - 2 @@ -97,34 +108,70 @@ - + - Last member query count + MLD version + + 1 2 + - u32:1-255 - Count + 1 + MLD version 1 + + + 2 + MLD version 2 - + + 2 - + + + + + #include + #include + #include + #include + + + Rendezvous Point + + + + + Rendezvous Point address + + ipv6 + Rendezvous Point address + + + + + + + - Last member query interval + Group Address range - u32:100-6553500 - Last member query interval in milliseconds + ipv6net + Group Address range - + + + #include - + + #include - + 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 @@ + + + + + + + + + Show MLD (Multicast Listener Discovery) information + + + + + MLD group information + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + MLD interface information + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + MLD joined groups and sources + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + MLD statistics + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + + + + + 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 @@ + + + + + + + + + Show PIM (Protocol Independent Multicast) information + + + + + PIM cached bsm packets information + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + PIM boot-strap router information + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + PIM cached group-rp mappings information + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + PIM downstream channel info + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + PIM interfaces information + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + PIM join information + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + PIM interface local-membership + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + PIM neighbor information + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + PIM cached nexthop rpf information + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + PIM rendezvous point information + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + PIM reverse path forwarding information + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + PIM neighbor addresses + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + PIM state information + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + PIM statistics + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + PIM upstream information + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + PIM upstream join-desired + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + PIM upstream source reverse path forwarding + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + + + + + diff --git a/smoketest/scripts/cli/test_protocols_pim6.py b/smoketest/scripts/cli/test_protocols_pim6.py index 1be12836d..e22a7c722 100755 --- a/smoketest/scripts/cli/test_protocols_pim6.py +++ b/smoketest/scripts/cli/test_protocols_pim6.py @@ -24,18 +24,20 @@ 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]) @@ -43,68 +45,95 @@ class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase): # Verify FRR pim6d configuration for interface in interfaces: - config = self.getFRRconfig( - f'interface {interface}', daemon=PROCESS_NAME) + 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_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) + 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): - # commit changes interfaces = Section.interfaces('ethernet') - - # Use an invalid multiple group address + # Use an invalid multicast group address for interface in interfaces: - self.cli_set(base_path + ['interface', - interface, 'mld', 'join', 'fd00::1234']) + 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 multiple group address + # Use a valid multicast group address for interface in interfaces: - self.cli_set(base_path + ['interface', - interface, 'mld', 'join', 'ff18::1234']) + 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) + 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_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) + 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 index 6a1235ba5..2003a1014 100755 --- a/src/conf_mode/protocols_pim6.py +++ b/src/conf_mode/protocols_pim6.py @@ -15,18 +15,19 @@ # along with this program. If not, see . from ipaddress import IPv6Address +from ipaddress import IPv6Network from sys import exit -from typing import Optional -from vyos import ConfigError, airbag, frr -from vyos.config import Config, ConfigDict +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 @@ -44,11 +45,21 @@ def get_config(config=None): if interfaces_removed: pim6['interface_removed'] = list(interfaces_removed) - return pim6 + # 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 pim6 is None: + if not pim6 or 'deleted' in pim6: return for interface, interface_config in pim6.get('interface', {}).items(): @@ -60,13 +71,34 @@ def verify(pim6): 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 pim6 is None: + 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: @@ -83,13 +115,12 @@ def apply(pim6): 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) + 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: -- cgit v1.2.3