summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/configd-include.json1
-rw-r--r--data/templates/frr/igmp.frr.j241
-rw-r--r--data/templates/frr/pim6d.frr.j245
-rw-r--r--data/templates/frr/pimd.frr.j2115
-rw-r--r--data/templates/openvpn/server.conf.j22
-rw-r--r--interface-definitions/dns-forwarding.xml.in19
-rw-r--r--interface-definitions/include/pim/bsm.xml.i14
-rw-r--r--interface-definitions/include/pim/dr-priority.xml.i14
-rw-r--r--interface-definitions/include/pim/hello.xml.i14
-rw-r--r--interface-definitions/include/pim/join-prune-interval.xml.i15
-rw-r--r--interface-definitions/include/pim/keep-alive-timer.xml.i14
-rw-r--r--interface-definitions/include/pim/packets.xml.i15
-rw-r--r--interface-definitions/include/pim/passive.xml.i8
-rw-r--r--interface-definitions/include/pim/register-suppress-time.xml.i14
-rw-r--r--interface-definitions/include/policy/prefix-list.xml.i14
-rw-r--r--interface-definitions/include/policy/prefix-list6.xml.i14
-rw-r--r--interface-definitions/include/radius-server-ipv4-ipv6.xml.i22
-rw-r--r--interface-definitions/include/source-address-ipv4-ipv6-multi.xml.i22
-rw-r--r--interface-definitions/include/source-address-ipv4-multi.xml.i18
-rw-r--r--interface-definitions/include/version/pim-version.xml.i3
-rw-r--r--interface-definitions/protocols-igmp.xml.in95
-rw-r--r--interface-definitions/protocols-pim.xml.in166
-rw-r--r--interface-definitions/protocols-pim6.xml.in97
-rw-r--r--interface-definitions/system-login.xml.in16
-rw-r--r--interface-definitions/xml-component-version.xml.in1
-rw-r--r--op-mode-definitions/show-interfaces.xml.in6
-rw-r--r--op-mode-definitions/show-ip-igmp.xml.in12
-rw-r--r--op-mode-definitions/show-ip-pim.xml.in116
-rw-r--r--op-mode-definitions/show-ipv6-mld.xml.in42
-rw-r--r--op-mode-definitions/show-ipv6-pim.xml.in120
-rw-r--r--op-mode-definitions/show-vrf.xml.in14
-rw-r--r--python/vyos/frr.py6
-rw-r--r--python/vyos/ifconfig/interface.py10
-rw-r--r--python/vyos/progressbar.py33
-rw-r--r--python/vyos/remote.py43
-rw-r--r--python/vyos/utils/io.py10
-rw-r--r--smoketest/config-tests/igmp-pim-small17
-rw-r--r--smoketest/configs/igmp-pim-small84
-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.py192
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_pim6.py73
-rwxr-xr-xsrc/conf_mode/protocols_igmp.py140
-rwxr-xr-xsrc/conf_mode/protocols_pim.py207
-rwxr-xr-xsrc/conf_mode/protocols_pim6.py57
-rwxr-xr-xsrc/migration-scripts/pim/0-to-172
-rwxr-xr-xsrc/op_mode/generate_firewall_rule-resequence.py4
-rwxr-xr-xsrc/op_mode/interfaces.py56
-rwxr-xr-xsrc/op_mode/restart_frr.py4
49 files changed, 1497 insertions, 629 deletions
diff --git a/data/configd-include.json b/data/configd-include.json
index 84bc1f14e..a762a6d4c 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -44,7 +44,6 @@
"policy-local-route.py",
"protocols_bfd.py",
"protocols_bgp.py",
-"protocols_igmp.py",
"protocols_isis.py",
"protocols_mpls.py",
"protocols_nhrp.py",
diff --git a/data/templates/frr/igmp.frr.j2 b/data/templates/frr/igmp.frr.j2
deleted file mode 100644
index b75884484..000000000
--- a/data/templates/frr/igmp.frr.j2
+++ /dev/null
@@ -1,41 +0,0 @@
-!
-{% for iface in old_ifaces %}
-interface {{ iface }}
-{% for group in old_ifaces[iface].gr_join %}
-{% if old_ifaces[iface].gr_join[group] %}
-{% for source in old_ifaces[iface].gr_join[group] %}
- no ip igmp join {{ group }} {{ source }}
-{% endfor %}
-{% else %}
- no ip igmp join {{ group }}
-{% endif %}
-{% endfor %}
- no ip igmp
-!
-{% endfor %}
-{% for interface, interface_config in ifaces.items() %}
-interface {{ interface }}
-{% if interface_config.version %}
- ip igmp version {{ interface_config.version }}
-{% else %}
-{# IGMP default version 3 #}
- ip igmp
-{% endif %}
-{% if interface_config.query_interval %}
- ip igmp query-interval {{ interface_config.query_interval }}
-{% endif %}
-{% if interface_config.query_max_resp_time %}
- ip igmp query-max-response-time {{ interface_config.query_max_resp_time }}
-{% endif %}
-{% for group, sources in interface_config.gr_join.items() %}
-{% if sources is vyos_defined %}
-{% for source in sources %}
- ip igmp join {{ group }} {{ source }}
-{% endfor %}
-{% else %}
- ip igmp join {{ group }}
-{% endif %}
-{% endfor %}
-!
-{% endfor %}
-!
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/data/templates/frr/pimd.frr.j2 b/data/templates/frr/pimd.frr.j2
index cb2f2aa98..68edf4a5c 100644
--- a/data/templates/frr/pimd.frr.j2
+++ b/data/templates/frr/pimd.frr.j2
@@ -1,34 +1,95 @@
+{% 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 and iface_config.igmp.disable is not vyos_defined %}
+ ip igmp
+{% if iface_config.igmp.query_interval %}
+ ip igmp query-interval {{ iface_config.igmp.query_interval }}
+{% endif %}
+{% if iface_config.igmp.query_max_response_time %}
+ ip igmp query-max-response-time {{ iface_config.igmp.query_max_response_time }}
+{% endif %}
+{% if iface_config.igmp.version is vyos_defined %}
+ ip igmp version {{ iface_config.igmp.version }}
+{% endif %}
+{% if iface_config.igmp.join is vyos_defined %}
+{% for join, join_config in iface_config.igmp.join.items() %}
+{% if join_config.source_address is vyos_defined %}
+{% for source_address in join_config.source_address %}
+ ip igmp join {{ join }} {{ source_address }}
+{% endfor %}
+{% else %}
+ ip igmp join {{ join }}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% 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 keep-alive-timer {{ keep_alive_timer }}
+{% endif %}
+{% if packets is vyos_defined %}
+ip pim packets {{ packets }}
+{% endif %}
+{% if register_accept_list.prefix_list is vyos_defined %}
+ip pim register-accept-list {{ register_accept_list.prefix_list }}
+{% endif %}
+{% if register_suppress_time is vyos_defined %}
+ip pim register-suppress-time {{ register_suppress_time }}
+{% endif %}
+{% if rp.address is vyos_defined %}
+{% for address, address_config in rp.address.items() %}
+{% for group in address_config.group %}
+ip pim rp {{ address }} {{ group }}
+{% endfor %}
{% endfor %}
-{% endfor %}
-{% if pim.rp_keep_alive %}
-ip pim rp keep-alive-timer {{ pim.rp_keep_alive }}
+{% endif %}
+{% if rp.keep_alive_timer is vyos_defined %}
+ip pim rp keep-alive-timer {{ rp.keep_alive_timer }}
+{% endif %}
+{% if no_v6_secondary is vyos_defined %}
+no ip pim send-v6-secondary
+{% endif %}
+{% if spt_switchover.infinity_and_beyond is vyos_defined %}
+ip pim spt-switchover infinity-and-beyond {{ 'prefix-list ' ~ spt_switchover.infinity_and_beyond.prefix_list if spt_switchover.infinity_and_beyond.prefix_list is defined }}
+{% endif %}
+{% if ssm.prefix_list is vyos_defined %}
+ip pim ssm prefix-list {{ ssm.prefix_list }}
+{% endif %}
+!
+{% if igmp.watermark_warning is vyos_defined %}
+ip igmp watermark-warn {{ igmp.watermark_warning }}
{% endif %}
!
diff --git a/data/templates/openvpn/server.conf.j2 b/data/templates/openvpn/server.conf.j2
index 746155c37..c02411904 100644
--- a/data/templates/openvpn/server.conf.j2
+++ b/data/templates/openvpn/server.conf.j2
@@ -79,7 +79,7 @@ server {{ subnet | address_from_cidr }} {{ subnet | netmask_from_cidr }} {{ 'nop
{% if server.push_route is vyos_defined %}
{% for route, route_config in server.push_route.items() %}
{% if route | is_ipv4 %}
-push "route {{ route | address_from_cidr }} {{ route | netmask_from_cidr }} {{ subnet | first_host_address ~ ' ' ~ route_config.metric if route_config.metric is vyos_defined }}"
+push "route {{ route | address_from_cidr }} {{ route | netmask_from_cidr }} {{ 'vpn_gateway' ~ ' ' ~ route_config.metric if route_config.metric is vyos_defined }}"
{% elif route | is_ipv6 %}
push "route-ipv6 {{ route }}"
{% endif %}
diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in
index c4295317a..5ca02acef 100644
--- a/interface-definitions/dns-forwarding.xml.in
+++ b/interface-definitions/dns-forwarding.xml.in
@@ -684,25 +684,8 @@
<defaultValue>1500</defaultValue>
</leafNode>
#include <include/name-server-ipv4-ipv6-port.xml.i>
+ #include <include/source-address-ipv4-ipv6-multi.xml.i>
<leafNode name="source-address">
- <properties>
- <help>Local addresses from which to send DNS queries</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_local_ips.sh --both</script>
- </completionHelp>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 address from which to send traffic</description>
- </valueHelp>
- <valueHelp>
- <format>ipv6</format>
- <description>IPv6 address from which to send traffic</description>
- </valueHelp>
- <multi/>
- <constraint>
- <validator name="ip-address"/>
- </constraint>
- </properties>
<defaultValue>0.0.0.0 ::</defaultValue>
</leafNode>
<leafNode name="system">
diff --git a/interface-definitions/include/pim/bsm.xml.i b/interface-definitions/include/pim/bsm.xml.i
new file mode 100644
index 000000000..cc2cf14ca
--- /dev/null
+++ b/interface-definitions/include/pim/bsm.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from pim/bsm.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>
+<!-- include end -->
diff --git a/interface-definitions/include/pim/dr-priority.xml.i b/interface-definitions/include/pim/dr-priority.xml.i
new file mode 100644
index 000000000..e4b3067c2
--- /dev/null
+++ b/interface-definitions/include/pim/dr-priority.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from pim/dr-priority.xml.i -->
+<leafNode name="dr-priority">
+ <properties>
+ <help>Designated router election priority</help>
+ <valueHelp>
+ <format>u32:1-4294967295</format>
+ <description>DR Priority</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-4294967295"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/pim/hello.xml.i b/interface-definitions/include/pim/hello.xml.i
new file mode 100644
index 000000000..0c7601be7
--- /dev/null
+++ b/interface-definitions/include/pim/hello.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from pim/hello.xml.i -->
+<leafNode name="hello">
+ <properties>
+ <help>Hello Interval</help>
+ <valueHelp>
+ <format>u32:1-180</format>
+ <description>Hello Interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-180"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/pim/join-prune-interval.xml.i b/interface-definitions/include/pim/join-prune-interval.xml.i
new file mode 100644
index 000000000..882787d3f
--- /dev/null
+++ b/interface-definitions/include/pim/join-prune-interval.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from pim/join-prune-interval.xml.i -->
+<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>
+<!-- include end -->
diff --git a/interface-definitions/include/pim/keep-alive-timer.xml.i b/interface-definitions/include/pim/keep-alive-timer.xml.i
new file mode 100644
index 000000000..0dd27d6e7
--- /dev/null
+++ b/interface-definitions/include/pim/keep-alive-timer.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from pim/keep-alive-timer.xml.i -->
+<leafNode name="keep-alive-timer">
+ <properties>
+ <help>Keep alive Timer</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Keep alive Timer in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/pim/packets.xml.i b/interface-definitions/include/pim/packets.xml.i
new file mode 100644
index 000000000..1dc00c971
--- /dev/null
+++ b/interface-definitions/include/pim/packets.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from pim/packets.xml.i -->
+<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>
+<!-- include end -->
diff --git a/interface-definitions/include/pim/passive.xml.i b/interface-definitions/include/pim/passive.xml.i
new file mode 100644
index 000000000..e4e9ca0b1
--- /dev/null
+++ b/interface-definitions/include/pim/passive.xml.i
@@ -0,0 +1,8 @@
+<!-- include start from pim/passive.xml.i -->
+<leafNode name="passive">
+ <properties>
+ <help>Disable sending and receiving PIM control packets on the interface</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/pim/register-suppress-time.xml.i b/interface-definitions/include/pim/register-suppress-time.xml.i
new file mode 100644
index 000000000..919945b52
--- /dev/null
+++ b/interface-definitions/include/pim/register-suppress-time.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from pim/register-suppress-time.xml.i -->
+<leafNode name="register-suppress-time">
+ <properties>
+ <help>Register suppress timer</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Timer in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/policy/prefix-list.xml.i b/interface-definitions/include/policy/prefix-list.xml.i
new file mode 100644
index 000000000..5d7980ee2
--- /dev/null
+++ b/interface-definitions/include/policy/prefix-list.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from policy/prefix-list.xml.i -->
+<leafNode name="prefix-list">
+ <properties>
+ <help>Prefix-list to use</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Prefix-list to apply (IPv4)</description>
+ </valueHelp>
+ <completionHelp>
+ <path>policy prefix-list</path>
+ </completionHelp>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/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/include/radius-server-ipv4-ipv6.xml.i b/interface-definitions/include/radius-server-ipv4-ipv6.xml.i
index a0cdcd7c3..e454b9025 100644
--- a/interface-definitions/include/radius-server-ipv4-ipv6.xml.i
+++ b/interface-definitions/include/radius-server-ipv4-ipv6.xml.i
@@ -25,27 +25,7 @@
#include <include/radius-server-auth-port.xml.i>
</children>
</tagNode>
- <leafNode name="source-address">
- <properties>
- <help>Source IP address used to initiate connection</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_local_ips.sh --both</script>
- </completionHelp>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 source address</description>
- </valueHelp>
- <valueHelp>
- <format>ipv6</format>
- <description>IPv6 source address</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
- </constraint>
- <multi/>
- </properties>
- </leafNode>
+ #include <include/source-address-ipv4-ipv6-multi.xml.i>
<leafNode name="security-mode">
<properties>
<help>Security mode for RADIUS authentication</help>
diff --git a/interface-definitions/include/source-address-ipv4-ipv6-multi.xml.i b/interface-definitions/include/source-address-ipv4-ipv6-multi.xml.i
new file mode 100644
index 000000000..d56ca5be6
--- /dev/null
+++ b/interface-definitions/include/source-address-ipv4-ipv6-multi.xml.i
@@ -0,0 +1,22 @@
+<!-- include start from source-address-ipv4-ipv6-multi.xml.i -->
+<leafNode name="source-address">
+ <properties>
+ <help>Source IP address used to initiate connection</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_local_ips.sh --both</script>
+ </completionHelp>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 source address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 source address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-address"/>
+ </constraint>
+ <multi/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/source-address-ipv4-multi.xml.i b/interface-definitions/include/source-address-ipv4-multi.xml.i
new file mode 100644
index 000000000..319a118f3
--- /dev/null
+++ b/interface-definitions/include/source-address-ipv4-multi.xml.i
@@ -0,0 +1,18 @@
+<!-- include start from source-address-ipv4-multi.xml.i -->
+<leafNode name="source-address">
+ <properties>
+ <help>IPv4 source address used to initiate connection</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_local_ips.sh --ipv4</script>
+ </completionHelp>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 source address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ <multi/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/version/pim-version.xml.i b/interface-definitions/include/version/pim-version.xml.i
new file mode 100644
index 000000000..24cc38cdf
--- /dev/null
+++ b/interface-definitions/include/version/pim-version.xml.i
@@ -0,0 +1,3 @@
+<!-- include start from include/version/pim-version.xml.i -->
+<syntaxVersion component='pim' version='1'></syntaxVersion>
+<!-- include end -->
diff --git a/interface-definitions/protocols-igmp.xml.in b/interface-definitions/protocols-igmp.xml.in
deleted file mode 100644
index a055db71e..000000000
--- a/interface-definitions/protocols-igmp.xml.in
+++ /dev/null
@@ -1,95 +0,0 @@
-<?xml version="1.0"?>
-<!-- Internet Group Management Protocol (IGMP) configuration -->
-<interfaceDefinition>
- <node name="protocols">
- <children>
- <node name="igmp" owner="${vyos_conf_scripts_dir}/protocols_igmp.py">
- <properties>
- <help>Internet Group Management Protocol (IGMP)</help>
- </properties>
- <children>
- <tagNode name="interface">
- <properties>
- <help>IGMP interface</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces</script>
- </completionHelp>
- </properties>
- <children>
- <tagNode name="join">
- <properties>
- <help>IGMP join multicast group</help>
- <valueHelp>
- <format>ipv4</format>
- <description>Multicast group address</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
- <children>
- <leafNode name="source">
- <properties>
- <help>Source address</help>
- <valueHelp>
- <format>ipv4</format>
- <description>Source address</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- <multi/>
- </properties>
- </leafNode>
- </children>
- </tagNode>
- <leafNode name="version">
- <properties>
- <help>IGMP version</help>
- <completionHelp>
- <list>2 3</list>
- </completionHelp>
- <valueHelp>
- <format>2</format>
- <description>IGMP version 2</description>
- </valueHelp>
- <valueHelp>
- <format>3</format>
- <description>IGMP version 3</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 2-3"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="query-interval">
- <properties>
- <help>IGMP host query interval</help>
- <valueHelp>
- <format>u32:1-1800</format>
- <description>Query interval in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-1800"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="query-max-response-time">
- <properties>
- <help>IGMP max query response time</help>
- <valueHelp>
- <format>u32:10-250</format>
- <description>Query response value in deci-seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 10-250"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
- </tagNode>
- </children>
- </node>
- </children>
- </node>
-</interfaceDefinition>
diff --git a/interface-definitions/protocols-pim.xml.in b/interface-definitions/protocols-pim.xml.in
index e9475930c..4a20c0d9b 100644
--- a/interface-definitions/protocols-pim.xml.in
+++ b/interface-definitions/protocols-pim.xml.in
@@ -5,7 +5,7 @@
<children>
<node name="pim" owner="${vyos_conf_scripts_dir}/protocols_pim.py">
<properties>
- <help>Protocol Independent Multicast (PIM)</help>
+ <help>Protocol Independent Multicast (PIM) and IGMP</help>
<priority>400</priority>
</properties>
<children>
@@ -15,34 +15,130 @@
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
+ <constraint>
+ #include <include/constraint/interface-name.xml.i>
+ </constraint>
</properties>
<children>
- <leafNode name="dr-priority">
+ #include <include/bfd/bfd.xml.i>
+ #include <include/pim/bsm.xml.i>
+ #include <include/pim/dr-priority.xml.i>
+ #include <include/pim/hello.xml.i>
+ #include <include/pim/passive.xml.i>
+ #include <include/source-address-ipv4.xml.i>
+ <node name="igmp">
+ <properties>
+ <help>Internet Group Management Protocol (IGMP) options</help>
+ </properties>
+ <children>
+ #include <include/generic-disable-node.xml.i>
+ <tagNode name="join">
+ <properties>
+ <help>IGMP join multicast group</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Multicast group address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/source-address-ipv4-multi.xml.i>
+ </children>
+ </tagNode>
+ <leafNode name="query-interval">
+ <properties>
+ <help>IGMP host query interval</help>
+ <valueHelp>
+ <format>u32:1-1800</format>
+ <description>Query interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-1800"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="query-max-response-time">
+ <properties>
+ <help>IGMP max query response time</help>
+ <valueHelp>
+ <format>u32:10-250</format>
+ <description>Query response value in deci-seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 10-250"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="version">
+ <properties>
+ <help>Interface IGMP version</help>
+ <completionHelp>
+ <list>2 3</list>
+ </completionHelp>
+ <valueHelp>
+ <format>2</format>
+ <description>IGMP version 2</description>
+ </valueHelp>
+ <valueHelp>
+ <format>3</format>
+ <description>IGMP version 3</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 2-3"/>
+ </constraint>
+ </properties>
+ <defaultValue>3</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ <node name="ecmp">
+ <properties>
+ <help>Enable PIM ECMP</help>
+ </properties>
+ <children>
+ <leafNode name="rebalance">
<properties>
- <help>Designated Router Election Priority</help>
- <valueHelp>
- <format>u32:1-4294967295</format>
- <description>Value of the new DR Priority</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-4294967295"/>
- </constraint>
+ <help>Enable PIM ECMP Rebalance</help>
+ <valueless/>
</properties>
</leafNode>
- <leafNode name="hello">
+ </children>
+ </node>
+ <node name="igmp">
+ <properties>
+ <help>Internet Group Management Protocol (IGMP) options</help>
+ </properties>
+ <children>
+ <leafNode name="watermark-warning">
<properties>
- <help>Hello Interval</help>
+ <help>Configure group limit for watermark warning</help>
<valueHelp>
- <format>u32:1-180</format>
- <description>Hello Interval in seconds</description>
+ <format>u32:1-65535</format>
+ <description>Group count to generate watermark warning</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 1-180"/>
+ <validator name="numeric" argument="--range 1-65535"/>
</constraint>
</properties>
</leafNode>
</children>
- </tagNode>
+ </node>
+ #include <include/pim/join-prune-interval.xml.i>
+ #include <include/pim/keep-alive-timer.xml.i>
+ #include <include/pim/packets.xml.i>
+ #include <include/pim/register-suppress-time.xml.i>
+ <node name="register-accept-list">
+ <properties>
+ <help>Only accept registers from a specific source prefix list</help>
+ </properties>
+ <children>
+ #include <include/policy/prefix-list.xml.i>
+ </children>
+ </node>
<node name="rp">
<properties>
<help>Rendezvous Point</help>
@@ -75,18 +171,36 @@
</leafNode>
</children>
</tagNode>
- <leafNode name="keep-alive-timer">
+ #include <include/pim/keep-alive-timer.xml.i>
+ </children>
+ </node>
+ <leafNode name="no-v6-secondary">
+ <properties>
+ <help>Disable IPv6 secondary address in hello packets</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <node name="spt-switchover">
+ <properties>
+ <help>Shortest-path tree (SPT) switchover</help>
+ </properties>
+ <children>
+ <node name="infinity-and-beyond">
<properties>
- <help>Keep alive Timer</help>
- <valueHelp>
- <format>u32:31-60000</format>
- <description>Keep alive Timer in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 31-60000"/>
- </constraint>
+ <help>Never switch to SPT Tree</help>
</properties>
- </leafNode>
+ <children>
+ #include <include/policy/prefix-list.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="ssm">
+ <properties>
+ <help>Source-Specific Multicast</help>
+ </properties>
+ <children>
+ #include <include/policy/prefix-list.xml.i>
</children>
</node>
</children>
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 @@
<children>
<node name="pim6" owner="${vyos_conf_scripts_dir}/protocols_pim6.py">
<properties>
- <help>Protocol Independent Multicast for IPv6 (PIMv6)</help>
+ <help>Protocol Independent Multicast for IPv6 (PIMv6) and MLD</help>
<priority>400</priority>
</properties>
<children>
@@ -15,8 +15,15 @@
<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>
@@ -53,25 +60,29 @@
</leafNode>
</children>
</tagNode>
- <leafNode name="version">
+ <leafNode name="last-member-query-count">
<properties>
- <help>MLD version</help>
- <completionHelp>
- <list>1 2</list>
- </completionHelp>
+ <help>Last member query count</help>
<valueHelp>
- <format>1</format>
- <description>MLD version 1</description>
+ <format>u32:1-255</format>
+ <description>Count</description>
</valueHelp>
- <valueHelp>
- <format>2</format>
- <description>MLD version 2</description>
+ <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 1-2"/>
+ <validator name="numeric" argument="--range 100-6553500"/>
</constraint>
</properties>
- <defaultValue>2</defaultValue>
</leafNode>
<leafNode name="interval">
<properties>
@@ -97,34 +108,70 @@
</constraint>
</properties>
</leafNode>
- <leafNode name="last-member-query-count">
+ <leafNode name="version">
<properties>
- <help>Last member query count</help>
+ <help>MLD version</help>
+ <completionHelp>
+ <list>1 2</list>
+ </completionHelp>
<valueHelp>
- <format>u32:1-255</format>
- <description>Count</description>
+ <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-255"/>
+ <validator name="numeric" argument="--range 1-2"/>
</constraint>
</properties>
+ <defaultValue>2</defaultValue>
</leafNode>
- <leafNode name="last-member-query-interval">
+ </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>Last member query interval</help>
+ <help>Group Address range</help>
<valueHelp>
- <format>u32:100-6553500</format>
- <description>Last member query interval in milliseconds</description>
+ <format>ipv6net</format>
+ <description>Group Address range</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 100-6553500"/>
+ <validator name="ipv6-prefix"/>
</constraint>
+ <multi/>
</properties>
</leafNode>
+ #include <include/policy/prefix-list6.xml.i>
</children>
- </node>
+ </tagNode>
+ #include <include/pim/keep-alive-timer.xml.i>
</children>
- </tagNode>
+ </node>
</children>
</node>
</children>
diff --git a/interface-definitions/system-login.xml.in b/interface-definitions/system-login.xml.in
index 30fea91b0..a2f8beead 100644
--- a/interface-definitions/system-login.xml.in
+++ b/interface-definitions/system-login.xml.in
@@ -244,21 +244,7 @@
</leafNode>
</children>
</tagNode>
- <leafNode name="source-address">
- <properties>
- <help>Source IP used to communicate with TACACS+ server</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_local_ips.sh --ipv4</script>
- </completionHelp>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 source address</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
- </leafNode>
+ #include <include/source-address-ipv4.xml.i>
<leafNode name="security-mode">
<properties>
<help>Security mode for TACACS+ authentication</help>
diff --git a/interface-definitions/xml-component-version.xml.in b/interface-definitions/xml-component-version.xml.in
index cae3423dc..10a1be242 100644
--- a/interface-definitions/xml-component-version.xml.in
+++ b/interface-definitions/xml-component-version.xml.in
@@ -30,6 +30,7 @@
#include <include/version/ntp-version.xml.i>
#include <include/version/openconnect-version.xml.i>
#include <include/version/ospf-version.xml.i>
+ #include <include/version/pim-version.xml.i>
#include <include/version/policy-version.xml.i>
#include <include/version/pppoe-server-version.xml.i>
#include <include/version/pptp-version.xml.i>
diff --git a/op-mode-definitions/show-interfaces.xml.in b/op-mode-definitions/show-interfaces.xml.in
index dc61a6f5c..b58e0efea 100644
--- a/op-mode-definitions/show-interfaces.xml.in
+++ b/op-mode-definitions/show-interfaces.xml.in
@@ -20,6 +20,12 @@
</properties>
<command>${vyos_op_scripts_dir}/interfaces.py show</command>
</leafNode>
+ <leafNode name="summary">
+ <properties>
+ <help>Show summary information of all interfaces</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/interfaces.py show_summary_extended</command>
+ </leafNode>
</children>
</node>
</children>
diff --git a/op-mode-definitions/show-ip-igmp.xml.in b/op-mode-definitions/show-ip-igmp.xml.in
index 855c5d508..1fd86ba54 100644
--- a/op-mode-definitions/show-ip-igmp.xml.in
+++ b/op-mode-definitions/show-ip-igmp.xml.in
@@ -13,31 +13,31 @@
<properties>
<help>IGMP groups information</help>
</properties>
- <command>vtysh -c "show ip igmp groups"</command>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
</leafNode>
- <leafNode name="interfaces">
+ <leafNode name="interface">
<properties>
<help>IGMP interfaces information</help>
</properties>
- <command>vtysh -c "show ip igmp interface"</command>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
</leafNode>
<leafNode name="join">
<properties>
<help>IGMP static join information</help>
</properties>
- <command>vtysh -c "show ip igmp join"</command>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
</leafNode>
<leafNode name="sources">
<properties>
<help>IGMP sources information</help>
</properties>
- <command>vtysh -c "show ip igmp sources"</command>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
</leafNode>
<leafNode name="statistics">
<properties>
<help>IGMP statistics</help>
</properties>
- <command>vtysh -c "show ip igmp statistics"</command>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
</leafNode>
</children>
</node>
diff --git a/op-mode-definitions/show-ip-pim.xml.in b/op-mode-definitions/show-ip-pim.xml.in
index fa317a944..9deba1f07 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="interfaces">
+ <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="interface">
<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/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/op-mode-definitions/show-vrf.xml.in b/op-mode-definitions/show-vrf.xml.in
index 9728eb1fa..c18649844 100644
--- a/op-mode-definitions/show-vrf.xml.in
+++ b/op-mode-definitions/show-vrf.xml.in
@@ -7,6 +7,14 @@
<help>Show VRF (Virtual Routing and Forwarding) information</help>
</properties>
<command>${vyos_op_scripts_dir}/vrf.py show</command>
+ <children>
+ <leafNode name="vni">
+ <properties>
+ <help>Show information on VRF/VXLAN VNI mapping</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
+ </children>
</node>
<tagNode name="vrf">
<properties>
@@ -23,6 +31,12 @@
</properties>
<command>ip vrf pids "$3"</command>
</leafNode>
+ <leafNode name="vni">
+ <properties>
+ <help>Show VXLAN VNI association</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
</children>
</tagNode>
</children>
diff --git a/python/vyos/frr.py b/python/vyos/frr.py
index ad5c207f5..a01d967e4 100644
--- a/python/vyos/frr.py
+++ b/python/vyos/frr.py
@@ -86,12 +86,8 @@ ch2 = logging.StreamHandler(stream=sys.stdout)
LOG.addHandler(ch)
LOG.addHandler(ch2)
-# Full list of FRR 9.0/stable daemons for reference
-#_frr_daemons = ['zebra', 'staticd', 'bgpd', 'ospfd', 'ospf6d', 'ripd', 'ripngd',
-# 'isisd', 'pim6d', 'ldpd', 'eigrpd', 'babeld', 'sharpd', 'bfdd',
-# 'fabricd', 'pathd']
_frr_daemons = ['zebra', 'staticd', 'bgpd', 'ospfd', 'ospf6d', 'ripd', 'ripngd',
- 'isisd', 'pim6d', 'ldpd', 'babeld', 'bfdd']
+ 'isisd', 'pimd', 'pim6d', 'ldpd', 'eigrpd', 'babeld', 'bfdd']
path_vtysh = '/usr/bin/vtysh'
path_frr_reload = '/usr/lib/frr/frr-reload.py'
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 050095364..31640385c 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -571,6 +571,16 @@ class Interface(Control):
self._cmd(f'ip link set dev {self.ifname} netns {netns}')
return True
+ def get_vrf(self):
+ """
+ Get VRF from interface
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').get_vrf()
+ """
+ return self.get_interface('vrf')
+
def set_vrf(self, vrf: str) -> bool:
"""
Add/Remove interface from given VRF instance.
diff --git a/python/vyos/progressbar.py b/python/vyos/progressbar.py
index 1793c445b..7bc9d9856 100644
--- a/python/vyos/progressbar.py
+++ b/python/vyos/progressbar.py
@@ -19,26 +19,35 @@ import signal
import subprocess
import sys
+from vyos.utils.io import is_dumb_terminal
from vyos.utils.io import print_error
+
class Progressbar:
def __init__(self, step=None):
self.total = 0.0
self.step = step
+ # Silently ignore all calls if terminal capabilities are lacking.
+ # This will also prevent the output from littering Ansible logs,
+ # as `ansible.netcommon.network_cli' coaxes the terminal into believing
+ # it is interactive.
+ self._dumb = is_dumb_terminal()
def __enter__(self):
- # Recalculate terminal width with every window resize.
- signal.signal(signal.SIGWINCH, lambda signum, frame: self._update_cols())
- # Disable line wrapping to prevent the staircase effect.
- subprocess.run(['tput', 'rmam'], check=False)
- self._update_cols()
- # Print an empty progressbar with entry.
- self.progress(0, 1)
+ if not self._dumb:
+ # Recalculate terminal width with every window resize.
+ signal.signal(signal.SIGWINCH, lambda signum, frame: self._update_cols())
+ # Disable line wrapping to prevent the staircase effect.
+ subprocess.run(['tput', 'rmam'], check=False)
+ self._update_cols()
+ # Print an empty progressbar with entry.
+ self.progress(0, 1)
return self
def __exit__(self, exc_type, kexc_val, exc_tb):
- # Revert to the default SIGWINCH handler (ie nothing).
- signal.signal(signal.SIGWINCH, signal.SIG_DFL)
- # Reenable line wrapping.
- subprocess.run(['tput', 'smam'], check=False)
+ if not self._dumb:
+ # Revert to the default SIGWINCH handler (ie nothing).
+ signal.signal(signal.SIGWINCH, signal.SIG_DFL)
+ # Reenable line wrapping.
+ subprocess.run(['tput', 'smam'], check=False)
def _update_cols(self):
# `os.get_terminal_size()' is fast enough for our purposes.
self.col = max(os.get_terminal_size().columns - 15, 20)
@@ -60,7 +69,7 @@ class Progressbar:
Stateless progressbar taking no input at init and current progress with
final size at callback (for SSH)
"""
- if done <= total:
+ if done <= total and not self._dumb:
length = math.ceil(self.col * done / total)
percentage = str(math.ceil(100 * done / total)).rjust(3)
# Carriage return at the end will make sure the line will get overwritten.
diff --git a/python/vyos/remote.py b/python/vyos/remote.py
index 1ca8a9530..4be477d24 100644
--- a/python/vyos/remote.py
+++ b/python/vyos/remote.py
@@ -34,6 +34,7 @@ from requests.packages.urllib3 import PoolManager
from vyos.progressbar import Progressbar
from vyos.utils.io import ask_yes_no
+from vyos.utils.io import is_interactive
from vyos.utils.io import print_error
from vyos.utils.misc import begin
from vyos.utils.process import cmd
@@ -49,7 +50,7 @@ class InteractivePolicy(MissingHostKeyPolicy):
def missing_host_key(self, client, hostname, key):
print_error(f"Host '{hostname}' not found in known hosts.")
print_error('Fingerprint: ' + key.get_fingerprint().hex())
- if sys.stdout.isatty() and ask_yes_no('Do you wish to continue?'):
+ if is_interactive() and ask_yes_no('Do you wish to continue?'):
if client._host_keys_filename\
and ask_yes_no('Do you wish to permanently add this host/key pair to known hosts?'):
client._host_keys.add(hostname, key.get_name(), key)
@@ -250,7 +251,6 @@ class HttpC:
allow_redirects=True,
timeout=self.timeout) as r:
# Abort early if the destination is inaccessible.
- print('pre-3')
r.raise_for_status()
# If the request got redirected, keep the last URL we ended up with.
final_urlstring = r.url
@@ -323,17 +323,25 @@ def urlc(urlstring, *args, **kwargs):
except KeyError:
raise ValueError(f'Unsupported URL scheme: "{url.scheme}"')
-def download(local_path, urlstring, *args, **kwargs):
+def download(local_path, urlstring, progressbar=False, check_space=False,
+ source_host='', source_port=0, timeout=10.0):
try:
- urlc(urlstring, *args, **kwargs).download(local_path)
+ progressbar = progressbar and is_interactive()
+ urlc(urlstring, progressbar, check_space, source_host, source_port, timeout).download(local_path)
except Exception as err:
print_error(f'Unable to download "{urlstring}": {err}')
+ except KeyboardInterrupt:
+ print_error('\nDownload aborted by user.')
-def upload(local_path, urlstring, *args, **kwargs):
+def upload(local_path, urlstring, progressbar=False,
+ source_host='', source_port=0, timeout=10.0):
try:
- urlc(urlstring, *args, **kwargs).upload(local_path)
+ progressbar = progressbar and is_interactive()
+ urlc(urlstring, progressbar, source_host, source_port, timeout).upload(local_path)
except Exception as err:
print_error(f'Unable to upload "{urlstring}": {err}')
+ except KeyboardInterrupt:
+ print_error('\nUpload aborted by user.')
def get_remote_config(urlstring, source_host='', source_port=0):
"""
@@ -346,26 +354,3 @@ def get_remote_config(urlstring, source_host='', source_port=0):
return f.read()
finally:
os.remove(temp)
-
-def friendly_download(local_path, urlstring, source_host='', source_port=0):
- """
- Download with a progress bar, reassuring messages and free space checks.
- """
- try:
- print_error('Downloading...')
- download(local_path, urlstring, True, True, source_host, source_port)
- except KeyboardInterrupt:
- print_error('\nDownload aborted by user.')
- sys.exit(1)
- except:
- import traceback
- print_error(f'Failed to download {urlstring}.')
- # There are a myriad different reasons a download could fail.
- # SSH errors, FTP errors, I/O errors, HTTP errors (403, 404...)
- # We omit the scary stack trace but print the error nevertheless.
- exc_type, exc_value, exc_traceback = sys.exc_info()
- traceback.print_exception(exc_type, exc_value, None, 0, None, False)
- sys.exit(1)
- else:
- print_error('Download complete.')
- sys.exit(0)
diff --git a/python/vyos/utils/io.py b/python/vyos/utils/io.py
index 5fffa62f8..8790cbaac 100644
--- a/python/vyos/utils/io.py
+++ b/python/vyos/utils/io.py
@@ -62,3 +62,13 @@ def ask_yes_no(question, default=False) -> bool:
stdout.write("Please respond with yes/y or no/n\n")
except EOFError:
stdout.write("\nPlease respond with yes/y or no/n\n")
+
+def is_interactive():
+ """Try to determine if the routine was called from an interactive shell."""
+ import os, sys
+ return os.getenv('TERM', default=False) and sys.stderr.isatty() and sys.stdout.isatty()
+
+def is_dumb_terminal():
+ """Check if the current TTY is dumb, so that we can disable advanced terminal features."""
+ import os
+ return os.getenv('TERM') in ['vt100', 'dumb']
diff --git a/smoketest/config-tests/igmp-pim-small b/smoketest/config-tests/igmp-pim-small
new file mode 100644
index 000000000..207c17d45
--- /dev/null
+++ b/smoketest/config-tests/igmp-pim-small
@@ -0,0 +1,17 @@
+set interfaces ethernet eth1 address '100.64.0.1/24'
+set interfaces ethernet eth2 address '172.16.0.2/24'
+set protocols pim interface eth1 igmp join 224.1.0.0 source-address '1.1.1.1'
+set protocols pim interface eth1 igmp join 224.1.0.0 source-address '1.1.1.2'
+set protocols pim interface eth1 igmp query-interval '1000'
+set protocols pim interface eth1 igmp query-max-response-time '30'
+set protocols pim interface eth1 igmp version '2'
+set protocols pim interface eth2
+set protocols pim rp address 172.16.255.1 group '224.0.0.0/4'
+set service ntp server 0.pool.ntp.org
+set service ntp server 1.pool.ntp.org
+set service ntp server 2.pool.ntp.org
+set system domain-name 'vyos.io'
+set system host-name 'vyos'
+set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/'
+set system login user vyos authentication plaintext-password ''
+set system console device ttyS0 speed '115200'
diff --git a/smoketest/configs/igmp-pim-small b/smoketest/configs/igmp-pim-small
new file mode 100644
index 000000000..f433ff8d7
--- /dev/null
+++ b/smoketest/configs/igmp-pim-small
@@ -0,0 +1,84 @@
+interfaces {
+ ethernet eth0 {
+ duplex auto
+ speed auto
+ }
+ ethernet eth1 {
+ address 100.64.0.1/24
+ duplex auto
+ speed auto
+ }
+ ethernet eth2 {
+ address 172.16.0.2/24
+ duplex auto
+ speed auto
+ }
+}
+protocols {
+ igmp {
+ interface eth1 {
+ join 224.1.0.0 {
+ source 1.1.1.1
+ source 1.1.1.2
+ }
+ query-interval 1000
+ query-max-response-time 30
+ version 2
+ }
+ }
+ pim {
+ interface eth1 {
+ }
+ interface eth2 {
+ }
+ rp {
+ address 172.16.255.1 {
+ group 224.0.0.0/4
+ }
+ }
+ }
+}
+system {
+ config-management {
+ commit-revisions 200
+ }
+ console {
+ device ttyS0 {
+ speed 115200
+ }
+ }
+ domain-name vyos.io
+ host-name vyos
+ login {
+ user vyos {
+ authentication {
+ encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/
+ plaintext-password ""
+ }
+ }
+ }
+ ntp {
+ server 0.pool.ntp.org {
+ }
+ server 1.pool.ntp.org {
+ }
+ server 2.pool.ntp.org {
+ }
+ }
+ syslog {
+ global {
+ facility all {
+ level info
+ }
+ facility protocols {
+ level debug
+ }
+ }
+ }
+ time-zone Europe/Berlin
+}
+
+
+// Warning: Do not remove the following line.
+// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@1:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@18:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@7:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@20:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1"
+// Release version: 1.3.0
diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py
index 51ccbc9e6..73b4e9764 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..ccfced138
--- /dev/null
+++ b/smoketest/scripts/cli/test_protocols_pim.py
@@ -0,0 +1,192 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.configsession import ConfigSessionError
+from vyos.ifconfig import Section
+from vyos.utils.process import process_named_running
+
+PROCESS_NAME = 'pimd'
+base_path = ['protocols', 'pim']
+
+class TestProtocolsPIM(VyOSUnitTestSHIM.TestCase):
+ def tearDown(self):
+ # pimd process must be running
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ # pimd process must be stopped by now
+ self.assertFalse(process_named_running(PROCESS_NAME))
+
+ def test_01_pim_basic(self):
+ rp = '127.0.0.1'
+ group = '224.0.0.0/4'
+ hello = '100'
+ dr_priority = '64'
+
+ self.cli_set(base_path + ['rp', 'address', rp, 'group', group])
+
+ interfaces = Section.interfaces('ethernet')
+ for interface in interfaces:
+ self.cli_set(base_path + ['interface', interface , 'bfd'])
+ self.cli_set(base_path + ['interface', interface , 'dr-priority', dr_priority])
+ self.cli_set(base_path + ['interface', interface , 'hello', hello])
+ self.cli_set(base_path + ['interface', interface , 'no-bsm'])
+ self.cli_set(base_path + ['interface', interface , 'no-unicast-bsm'])
+ self.cli_set(base_path + ['interface', interface , 'passive'])
+
+ # commit changes
+ self.cli_commit()
+
+ # Verify FRR pimd configuration
+ frrconfig = self.getFRRconfig(daemon=PROCESS_NAME)
+ self.assertIn(f'ip pim rp {rp} {group}', frrconfig)
+
+ for interface in interfaces:
+ frrconfig = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ self.assertIn(f'interface {interface}', frrconfig)
+ self.assertIn(f' ip pim', frrconfig)
+ self.assertIn(f' ip pim bfd', frrconfig)
+ self.assertIn(f' ip pim drpriority {dr_priority}', frrconfig)
+ self.assertIn(f' ip pim hello {hello}', frrconfig)
+ self.assertIn(f' no ip pim bsm', frrconfig)
+ self.assertIn(f' no ip pim unicast-bsm', frrconfig)
+ self.assertIn(f' ip pim passive', frrconfig)
+
+ self.cli_commit()
+
+ def test_02_pim_advanced(self):
+ rp = '127.0.0.2'
+ group = '224.0.0.0/4'
+ join_prune_interval = '123'
+ rp_keep_alive_timer = '190'
+ keep_alive_timer = '180'
+ packets = '10'
+ prefix_list = 'pim-test'
+ register_suppress_time = '300'
+
+ self.cli_set(base_path + ['rp', 'address', rp, 'group', group])
+ self.cli_set(base_path + ['rp', 'keep-alive-timer', rp_keep_alive_timer])
+
+ self.cli_set(base_path + ['ecmp', 'rebalance'])
+ self.cli_set(base_path + ['join-prune-interval', join_prune_interval])
+ self.cli_set(base_path + ['keep-alive-timer', keep_alive_timer])
+ self.cli_set(base_path + ['packets', packets])
+ self.cli_set(base_path + ['register-accept-list', 'prefix-list', prefix_list])
+ self.cli_set(base_path + ['register-suppress-time', register_suppress_time])
+ self.cli_set(base_path + ['no-v6-secondary'])
+ self.cli_set(base_path + ['spt-switchover', 'infinity-and-beyond', 'prefix-list', prefix_list])
+ self.cli_set(base_path + ['ssm', 'prefix-list', prefix_list])
+
+ # check validate() - PIM require defined interfaces!
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ interfaces = Section.interfaces('ethernet')
+ for interface in interfaces:
+ self.cli_set(base_path + ['interface', interface])
+
+ # commit changes
+ self.cli_commit()
+
+ # Verify FRR pimd configuration
+ frrconfig = self.getFRRconfig(daemon=PROCESS_NAME)
+ self.assertIn(f'ip pim rp {rp} {group}', frrconfig)
+ self.assertIn(f'ip pim rp keep-alive-timer {rp_keep_alive_timer}', frrconfig)
+ self.assertIn(f'ip pim ecmp rebalance', frrconfig)
+ self.assertIn(f'ip pim join-prune-interval {join_prune_interval}', frrconfig)
+ self.assertIn(f'ip pim keep-alive-timer {keep_alive_timer}', frrconfig)
+ self.assertIn(f'ip pim packets {packets}', frrconfig)
+ self.assertIn(f'ip pim register-accept-list {prefix_list}', frrconfig)
+ self.assertIn(f'ip pim register-suppress-time {register_suppress_time}', frrconfig)
+ self.assertIn(f'no ip pim send-v6-secondary', frrconfig)
+ self.assertIn(f'ip pim spt-switchover infinity-and-beyond prefix-list {prefix_list}', frrconfig)
+ self.assertIn(f'ip pim ssm prefix-list {prefix_list}', frrconfig)
+
+ def test_03_pim_igmp_proxy(self):
+ igmp_proxy = ['protocols', 'igmp-proxy']
+ rp = '127.0.0.1'
+ group = '224.0.0.0/4'
+
+ self.cli_set(base_path)
+ self.cli_set(igmp_proxy)
+
+ # check validate() - can not set both IGMP proxy and PIM
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_delete(igmp_proxy)
+
+ self.cli_set(base_path + ['rp', 'address', rp, 'group', group])
+ interfaces = Section.interfaces('ethernet')
+ for interface in interfaces:
+ self.cli_set(base_path + ['interface', interface , 'bfd'])
+
+ # commit changes
+ self.cli_commit()
+
+ def test_04_igmp(self):
+ watermark_warning = '2000'
+ query_interval = '1000'
+ query_max_response_time = '200'
+ version = '2'
+
+ igmp_join = {
+ '224.1.1.1' : { 'source' : ['1.1.1.1', '2.2.2.2', '3.3.3.3'] },
+ '224.1.2.2' : { 'source' : [] },
+ '224.1.3.3' : {},
+ }
+
+ self.cli_set(base_path + ['igmp', 'watermark-warning', watermark_warning])
+ interfaces = Section.interfaces('ethernet')
+ for interface in interfaces:
+ self.cli_set(base_path + ['interface', interface , 'igmp', 'version', version])
+ self.cli_set(base_path + ['interface', interface , 'igmp', 'query-interval', query_interval])
+ self.cli_set(base_path + ['interface', interface , 'igmp', 'query-max-response-time', query_max_response_time])
+
+ for join, join_config in igmp_join.items():
+ self.cli_set(base_path + ['interface', interface , 'igmp', 'join', join])
+ if 'source' in join_config:
+ for source in join_config['source']:
+ self.cli_set(base_path + ['interface', interface , 'igmp', 'join', join, 'source-address', source])
+
+ self.cli_commit()
+
+ frrconfig = self.getFRRconfig(daemon=PROCESS_NAME)
+ self.assertIn(f'ip igmp watermark-warn {watermark_warning}', frrconfig)
+
+ for interface in interfaces:
+ frrconfig = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ self.assertIn(f'interface {interface}', frrconfig)
+ self.assertIn(f' ip igmp', frrconfig)
+ self.assertIn(f' ip igmp version {version}', frrconfig)
+ self.assertIn(f' ip igmp query-interval {query_interval}', frrconfig)
+ self.assertIn(f' ip igmp query-max-response-time {query_max_response_time}', frrconfig)
+
+ for join, join_config in igmp_join.items():
+ if 'source' in join_config:
+ for source in join_config['source']:
+ self.assertIn(f' ip igmp join {join} {source}', frrconfig)
+ else:
+ self.assertIn(f' ip igmp join {join}', frrconfig)
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
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_igmp.py b/src/conf_mode/protocols_igmp.py
deleted file mode 100755
index 435189025..000000000
--- a/src/conf_mode/protocols_igmp.py
+++ /dev/null
@@ -1,140 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2020-2023 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-
-from ipaddress import IPv4Address
-from sys import exit
-
-from vyos import ConfigError
-from vyos.config import Config
-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 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/igmp.frr'
-
-def get_config(config=None):
- if config:
- conf = config
- else:
- conf = Config()
- igmp_conf = {
- 'igmp_conf' : False,
- 'pim_conf' : False,
- 'igmp_proxy_conf' : False,
- 'old_ifaces' : {},
- 'ifaces' : {}
- }
- if not (conf.exists('protocols igmp') or conf.exists_effective('protocols igmp')):
- return None
-
- if conf.exists('protocols igmp-proxy'):
- igmp_conf['igmp_proxy_conf'] = True
-
- if conf.exists('protocols pim'):
- igmp_conf['pim_conf'] = True
-
- if conf.exists('protocols igmp'):
- igmp_conf['igmp_conf'] = True
-
- conf.set_level('protocols igmp')
-
- # # Get interfaces
- for iface in conf.list_effective_nodes('interface'):
- igmp_conf['old_ifaces'].update({
- iface : {
- 'version' : conf.return_effective_value('interface {0} version'.format(iface)),
- 'query_interval' : conf.return_effective_value('interface {0} query-interval'.format(iface)),
- 'query_max_resp_time' : conf.return_effective_value('interface {0} query-max-response-time'.format(iface)),
- 'gr_join' : {}
- }
- })
- for gr_join in conf.list_effective_nodes('interface {0} join'.format(iface)):
- igmp_conf['old_ifaces'][iface]['gr_join'][gr_join] = conf.return_effective_values('interface {0} join {1} source'.format(iface, gr_join))
-
- for iface in conf.list_nodes('interface'):
- igmp_conf['ifaces'].update({
- iface : {
- 'version' : conf.return_value('interface {0} version'.format(iface)),
- 'query_interval' : conf.return_value('interface {0} query-interval'.format(iface)),
- 'query_max_resp_time' : conf.return_value('interface {0} query-max-response-time'.format(iface)),
- 'gr_join' : {}
- }
- })
- for gr_join in conf.list_nodes('interface {0} join'.format(iface)):
- igmp_conf['ifaces'][iface]['gr_join'][gr_join] = conf.return_values('interface {0} join {1} source'.format(iface, gr_join))
-
- return igmp_conf
-
-def verify(igmp):
- if igmp is None:
- return None
-
- if igmp['igmp_conf']:
- # Check conflict with IGMP-Proxy
- if igmp['igmp_proxy_conf']:
- raise ConfigError(f"IGMP proxy and PIM cannot be both configured at the same time")
-
- # Check interfaces
- if not igmp['ifaces']:
- raise ConfigError(f"IGMP require defined interfaces!")
- # Check, is this multicast group
- for intfc in igmp['ifaces']:
- for gr_addr in igmp['ifaces'][intfc]['gr_join']:
- if not IPv4Address(gr_addr).is_multicast:
- raise ConfigError(gr_addr + " not a multicast group")
-
-def generate(igmp):
- if igmp is None:
- return None
-
- render(config_file, 'frr/igmp.frr.j2', igmp)
- return None
-
-def apply(igmp):
- if igmp is None:
- return None
-
- pim_pid = process_named_running('pimd')
- if igmp['igmp_conf'] or igmp['pim_conf']:
- if not pim_pid:
- call(pimd_cmd)
-
- if os.path.exists(config_file):
- call(f'vtysh -d pimd -f {config_file}')
- os.remove(config_file)
- elif pim_pid:
- os.kill(int(pim_pid), SIGTERM)
-
- return None
-
-if __name__ == '__main__':
- try:
- c = get_config()
- verify(c)
- generate(c)
- apply(c)
- except ConfigError as e:
- print(e)
- exit(1)
diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py
index 0aaa0d2c6..09c3be8df 100755
--- a/src/conf_mode/protocols_pim.py
+++ b/src/conf_mode/protocols_pim.py
@@ -16,144 +16,139 @@
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 node_changed
+from vyos.configverify import verify_interface_exists
from vyos.utils.process import process_named_running
from vyos.utils.process import call
-from vyos.template import render
-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")
-
- # Check interfaces
- if not pim['pim']['ifaces']:
- raise ConfigError(f"PIM require defined interfaces!")
+ if 'igmp_proxy_enabled' in pim:
+ raise ConfigError('IGMP proxy and PIM cannot be configured at the same time!')
- if not pim['pim']['rp']:
- raise ConfigError(f"RP address required")
+ if 'interface' not in pim:
+ raise ConfigError('PIM require defined interfaces!')
- # 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")
+ for interface in pim['interface']:
+ verify_interface_exists(interface)
- # 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")
+ if 'rp' in pim:
+ if 'address' not in pim['rp']:
+ raise ConfigError('PIM rendezvous point needs to be defined!')
- uniq_groups.extend(pim['pim']['rp'][rp_addr])
+ # Check unique multicast groups
+ unique = []
+ pim_base_error = 'PIM rendezvous point group'
+ for address, address_config in pim['rp']['address'].items():
+ if 'group' not in address_config:
+ raise ConfigError(f'{pim_base_error} should be defined for "{address}"!')
+
+ # Check if it is a multicast group
+ for gr_addr in address_config['group']:
+ if not IPv4Network(gr_addr).is_multicast:
+ raise ConfigError(f'{pim_base_error} "{gr_addr}" is not a multicast group!')
+ if gr_addr in unique:
+ raise ConfigError(f'{pim_base_error} must be unique!')
+ unique.append(gr_addr)
def generate(pim):
- if 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/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 <http://www.gnu.org/licenses/>.
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:
diff --git a/src/migration-scripts/pim/0-to-1 b/src/migration-scripts/pim/0-to-1
new file mode 100755
index 000000000..bf8af733c
--- /dev/null
+++ b/src/migration-scripts/pim/0-to-1
@@ -0,0 +1,72 @@
+#!/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/>.
+
+# T5736: igmp: migrate "protocols igmp" to "protocols pim"
+
+import sys
+from vyos.configtree import ConfigTree
+
+if len(sys.argv) < 2:
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+base = ['protocols', 'igmp']
+pim_base = ['protocols', 'pim']
+if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+
+for interface in config.list_nodes(base + ['interface']):
+ base_igmp_iface = base + ['interface', interface]
+ pim_base_iface = pim_base + ['interface', interface]
+
+ # Create IGMP note under PIM interface
+ if not config.exists(pim_base_iface + ['igmp']):
+ config.set(pim_base_iface + ['igmp'])
+
+ if config.exists(base_igmp_iface + ['join']):
+ config.copy(base_igmp_iface + ['join'], pim_base_iface + ['igmp', 'join'])
+ config.set_tag(pim_base_iface + ['igmp', 'join'])
+
+ new_join_base = pim_base_iface + ['igmp', 'join']
+ for address in config.list_nodes(new_join_base):
+ if config.exists(new_join_base + [address, 'source']):
+ config.rename(new_join_base + [address, 'source'], 'source-address')
+
+ if config.exists(base_igmp_iface + ['query-interval']):
+ config.copy(base_igmp_iface + ['query-interval'], pim_base_iface + ['igmp', 'query-interval'])
+
+ if config.exists(base_igmp_iface + ['query-max-response-time']):
+ config.copy(base_igmp_iface + ['query-max-response-time'], pim_base_iface + ['igmp', 'query-max-response-time'])
+
+ if config.exists(base_igmp_iface + ['version']):
+ config.copy(base_igmp_iface + ['version'], pim_base_iface + ['igmp', 'version'])
+
+config.delete(base)
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/op_mode/generate_firewall_rule-resequence.py b/src/op_mode/generate_firewall_rule-resequence.py
index eb82a1a0a..21441f689 100755
--- a/src/op_mode/generate_firewall_rule-resequence.py
+++ b/src/op_mode/generate_firewall_rule-resequence.py
@@ -41,6 +41,10 @@ def convert_to_set_commands(config_dict, parent_key=''):
commands.extend(
convert_to_set_commands(value, f"{current_key} "))
+ elif isinstance(value, list):
+ for item in value:
+ commands.append(f"set {current_key} '{item}'")
+
elif isinstance(value, str):
commands.append(f"set {current_key} '{value}'")
diff --git a/src/op_mode/interfaces.py b/src/op_mode/interfaces.py
index 782e178c6..c626535b5 100755
--- a/src/op_mode/interfaces.py
+++ b/src/op_mode/interfaces.py
@@ -243,6 +243,9 @@ def _get_summary_data(ifname: typing.Optional[str],
res_intf['admin_state'] = interface.get_admin_state()
res_intf['addr'] = [_ for _ in interface.get_addr() if not _.startswith('fe80::')]
res_intf['description'] = interface.get_alias()
+ res_intf['mtu'] = interface.get_mtu()
+ res_intf['mac'] = interface.get_mac()
+ res_intf['vrf'] = interface.get_vrf()
ret.append(res_intf)
@@ -373,6 +376,51 @@ def _format_show_summary(data):
return 0
@catch_broken_pipe
+def _format_show_summary_extended(data):
+ headers = ["Interface", "IP Address", "MAC", "VRF", "MTU", "S/L", "Description"]
+ table_data = []
+
+ print('Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down')
+
+ for intf in data:
+ if 'unhandled' in intf:
+ continue
+
+ ifname = intf['ifname']
+ oper_state = 'u' if intf['oper_state'] in ('up', 'unknown') else 'D'
+ admin_state = 'u' if intf['admin_state'] in ('up', 'unknown') else 'A'
+ addrs = intf['addr'] or ['-']
+ description = '\n'.join(_split_text(intf['description'], 0))
+ mac = intf['mac'] if intf['mac'] else 'n/a'
+ mtu = intf['mtu'] if intf['mtu'] else 'n/a'
+ vrf = intf['vrf'] if intf['vrf'] else 'default'
+
+ ip_addresses = '\n'.join(ip for ip in addrs)
+
+ # Create a row for the table
+ row = [
+ ifname,
+ ip_addresses,
+ mac,
+ vrf,
+ mtu,
+ f"{admin_state}/{oper_state}",
+ description,
+ ]
+
+ # Append the row to the table data
+ table_data.append(row)
+
+ for intf in data:
+ if 'unhandled' in intf:
+ string = {'C': 'u/D', 'D': 'A/D'}[intf['state']]
+ table_data.append([intf['ifname'], '', '', '', '', string, ''])
+
+ print(tabulate(table_data, headers))
+
+ return 0
+
+@catch_broken_pipe
def _format_show_counters(data: list):
data_entries = []
for entry in data:
@@ -408,6 +456,14 @@ def show_summary(raw: bool, intf_name: typing.Optional[str],
return data
return _format_show_summary(data)
+def show_summary_extended(raw: bool, intf_name: typing.Optional[str],
+ intf_type: typing.Optional[str],
+ vif: bool, vrrp: bool):
+ data = _get_summary_data(intf_name, intf_type, vif, vrrp)
+ if raw:
+ return data
+ return _format_show_summary_extended(data)
+
def show_counters(raw: bool, intf_name: typing.Optional[str],
intf_type: typing.Optional[str],
vif: bool, vrrp: bool):
diff --git a/src/op_mode/restart_frr.py b/src/op_mode/restart_frr.py
index 820a3846c..8841b0eca 100755
--- a/src/op_mode/restart_frr.py
+++ b/src/op_mode/restart_frr.py
@@ -139,9 +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')
-# Full list of FRR 9.0/stable daemons for reference
-#cmd_args_parser.add_argument('--daemon', choices=['zebra', 'staticd', 'bgpd', 'ospfd', 'ospf6d', 'ripd', 'ripngd', 'isisd', 'pim6d', 'ldpd', 'eigrpd', 'babeld', 'sharpd', 'bfdd', 'fabricd', 'pathd'], required=False, nargs='*', help='select single or multiple daemons')
-cmd_args_parser.add_argument('--daemon', choices=['zebra', 'staticd', 'bgpd', 'ospfd', 'ospf6d', 'ripd', 'ripngd', 'isisd', 'pim6d', 'ldpd', 'babeld', 'bfdd'], 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()