summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/frr/bgpd.frr.j214
-rw-r--r--data/templates/frr/evpn.mh.frr.j228
-rw-r--r--data/templates/frr/fabricd.frr.j21
-rw-r--r--data/templates/frr/static_mcast.frr.j211
-rw-r--r--data/templates/frr/static_routes_macro.j231
-rw-r--r--data/templates/frr/staticd.frr.j297
-rw-r--r--data/templates/frr/zebra.vrf.route-map.frr.j22
-rw-r--r--interface-definitions/include/source-address-ipv4.xml.i2
-rw-r--r--interface-definitions/include/source-address-ipv6.xml.i17
-rw-r--r--interface-definitions/include/static/bfd-multi-hop.xml.i8
-rw-r--r--interface-definitions/include/static/static-route-bfd.xml.i36
-rw-r--r--interface-definitions/include/static/static-route.xml.i12
-rw-r--r--interface-definitions/include/static/static-route6.xml.i11
-rw-r--r--interface-definitions/protocols_static.xml.in56
-rw-r--r--interface-definitions/protocols_static_multicast.xml.in95
-rw-r--r--python/vyos/configdict.py465
-rw-r--r--python/vyos/configverify.py15
-rw-r--r--python/vyos/frrender.py156
-rwxr-xr-xsmoketest/scripts/cli/test_policy.py8
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bfd.py12
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_static.py110
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_static_multicast.py49
-rwxr-xr-xsrc/conf_mode/interfaces_bonding.py34
-rwxr-xr-xsrc/conf_mode/interfaces_ethernet.py32
-rwxr-xr-xsrc/conf_mode/policy.py129
-rwxr-xr-xsrc/conf_mode/protocols_babel.py79
-rwxr-xr-xsrc/conf_mode/protocols_bfd.py40
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py136
-rwxr-xr-xsrc/conf_mode/protocols_eigrp.py81
-rwxr-xr-xsrc/conf_mode/protocols_isis.py142
-rwxr-xr-xsrc/conf_mode/protocols_mpls.py42
-rw-r--r--src/conf_mode/protocols_openfabric.py63
-rwxr-xr-xsrc/conf_mode/protocols_ospf.py133
-rwxr-xr-xsrc/conf_mode/protocols_ospfv3.py124
-rwxr-xr-xsrc/conf_mode/protocols_rip.py78
-rwxr-xr-xsrc/conf_mode/protocols_ripng.py63
-rwxr-xr-xsrc/conf_mode/protocols_rpki.py49
-rwxr-xr-xsrc/conf_mode/protocols_segment-routing.py92
-rwxr-xr-xsrc/conf_mode/protocols_static.py82
-rwxr-xr-xsrc/conf_mode/protocols_static_multicast.py133
-rwxr-xr-xsrc/conf_mode/service_snmp.py11
-rwxr-xr-xsrc/conf_mode/vrf.py23
42 files changed, 1378 insertions, 1424 deletions
diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2
index 71716250d..51a3f2564 100644
--- a/data/templates/frr/bgpd.frr.j2
+++ b/data/templates/frr/bgpd.frr.j2
@@ -1,13 +1,19 @@
{### MACRO definition for recurring peer patter, this can be either fed by a ###}
{### peer-group or an individual BGP neighbor ###}
{% macro bgp_neighbor(neighbor, config, peer_group=false) %}
+{# BGP order of peer-group and remote-as placement must be honored #}
{% if peer_group == true %}
neighbor {{ neighbor }} peer-group
-{% elif config.peer_group is vyos_defined %}
- neighbor {{ neighbor }} peer-group {{ config.peer_group }}
-{% endif %}
-{% if config.remote_as is vyos_defined %}
+{% if config.remote_as is vyos_defined %}
+ neighbor {{ neighbor }} remote-as {{ config.remote_as }}
+{% endif %}
+{% else %}
+{% if config.remote_as is vyos_defined %}
neighbor {{ neighbor }} remote-as {{ config.remote_as }}
+{% endif %}
+{% if config.peer_group is vyos_defined %}
+ neighbor {{ neighbor }} peer-group {{ config.peer_group }}
+{% endif %}
{% endif %}
{% if config.local_role is vyos_defined %}
{% for role, strict in config.local_role.items() %}
diff --git a/data/templates/frr/evpn.mh.frr.j2 b/data/templates/frr/evpn.mh.frr.j2
index 03aaac44b..2fd7b7c09 100644
--- a/data/templates/frr/evpn.mh.frr.j2
+++ b/data/templates/frr/evpn.mh.frr.j2
@@ -1,16 +1,20 @@
!
-interface {{ ifname }}
-{% if evpn.es_df_pref is vyos_defined %}
- evpn mh es-df-pref {{ evpn.es_df_pref }}
-{% endif %}
-{% if evpn.es_id is vyos_defined %}
- evpn mh es-id {{ evpn.es_id }}
-{% endif %}
-{% if evpn.es_sys_mac is vyos_defined %}
- evpn mh es-sys-mac {{ evpn.es_sys_mac }}
-{% endif %}
-{% if evpn.uplink is vyos_defined %}
+{% if interfaces is vyos_defined %}
+{% for if_name, if_config in interfaces.items() %}
+interface {{ if_name }}
+{% if if_config.evpn.es_df_pref is vyos_defined %}
+ evpn mh es-df-pref {{ if_config.evpn.es_df_pref }}
+{% endif %}
+{% if if_config.evpn.es_id is vyos_defined %}
+ evpn mh es-id {{ if_config.evpn.es_id }}
+{% endif %}
+{% if if_config.evpn.es_sys_mac is vyos_defined %}
+ evpn mh es-sys-mac {{ if_config.evpn.es_sys_mac }}
+{% endif %}
+{% if if_config.evpn.uplink is vyos_defined %}
evpn mh uplink
-{% endif %}
+{% endif %}
exit
!
+{% endfor %}
+{% endif %}
diff --git a/data/templates/frr/fabricd.frr.j2 b/data/templates/frr/fabricd.frr.j2
index 8f2ae6466..3a0646eb8 100644
--- a/data/templates/frr/fabricd.frr.j2
+++ b/data/templates/frr/fabricd.frr.j2
@@ -70,3 +70,4 @@ router openfabric {{ name }}
exit
!
{% endfor %}
+!
diff --git a/data/templates/frr/static_mcast.frr.j2 b/data/templates/frr/static_mcast.frr.j2
deleted file mode 100644
index 54b2790b0..000000000
--- a/data/templates/frr/static_mcast.frr.j2
+++ /dev/null
@@ -1,11 +0,0 @@
-!
-{% for route_gr in mroute %}
-{% for nh in mroute[route_gr] %}
-{% if mroute[route_gr][nh] %}
-ip mroute {{ route_gr }} {{ nh }} {{ mroute[route_gr][nh] }}
-{% else %}
-ip mroute {{ route_gr }} {{ nh }}
-{% endif %}
-{% endfor %}
-{% endfor %}
-!
diff --git a/data/templates/frr/static_routes_macro.j2 b/data/templates/frr/static_routes_macro.j2
deleted file mode 100644
index db31700c5..000000000
--- a/data/templates/frr/static_routes_macro.j2
+++ /dev/null
@@ -1,31 +0,0 @@
-{% macro static_routes(ip_ipv6, prefix, prefix_config, table=None) %}
-{% if prefix_config.blackhole is vyos_defined %}
-{{ ip_ipv6 }} route {{ prefix }} blackhole {{ prefix_config.blackhole.distance if prefix_config.blackhole.distance is vyos_defined }} {{ 'tag ' ~ prefix_config.blackhole.tag if prefix_config.blackhole.tag is vyos_defined }} {{ 'table ' ~ table if table is vyos_defined and table is not none }}
-{% endif %}
-{% if prefix_config.reject is vyos_defined %}
-{{ ip_ipv6 }} route {{ prefix }} reject {{ prefix_config.reject.distance if prefix_config.reject.distance is vyos_defined }} {{ 'tag ' ~ prefix_config.reject.tag if prefix_config.reject.tag is vyos_defined }} {{ 'table ' ~ table if table is vyos_defined }}
-{% endif %}
-{% if prefix_config.dhcp_interface is vyos_defined %}
-{% for dhcp_interface in prefix_config.dhcp_interface %}
-{% set next_hop = dhcp_interface | get_dhcp_router %}
-{% if next_hop is vyos_defined %}
-{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ dhcp_interface }} {{ 'table ' ~ table if table is vyos_defined }}
-{% endif %}
-{% endfor %}
-{% endif %}
-{% if prefix_config.interface is vyos_defined %}
-{% for interface, interface_config in prefix_config.interface.items() if interface_config.disable is not defined %}
-{{ ip_ipv6 }} route {{ prefix }} {{ interface }} {{ interface_config.distance if interface_config.distance is vyos_defined }} {{ 'nexthop-vrf ' ~ interface_config.vrf if interface_config.vrf is vyos_defined }} {{ 'segments ' ~ interface_config.segments if interface_config.segments is vyos_defined }} {{ 'table ' ~ table if table is vyos_defined }}
-{% endfor %}
-{% endif %}
-{% if prefix_config.next_hop is vyos_defined and prefix_config.next_hop is not none %}
-{% for next_hop, next_hop_config in prefix_config.next_hop.items() if next_hop_config.disable is not defined %}
-{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ next_hop_config.interface if next_hop_config.interface is vyos_defined }} {{ next_hop_config.distance if next_hop_config.distance is vyos_defined }} {{ 'nexthop-vrf ' ~ next_hop_config.vrf if next_hop_config.vrf is vyos_defined }} {{ 'bfd profile ' ~ next_hop_config.bfd.profile if next_hop_config.bfd.profile is vyos_defined }} {{ 'segments ' ~ next_hop_config.segments if next_hop_config.segments is vyos_defined }} {{ 'table ' ~ table if table is vyos_defined }}
-{% if next_hop_config.bfd.multi_hop.source is vyos_defined %}
-{% for source, source_config in next_hop_config.bfd.multi_hop.source.items() %}
-{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} bfd multi-hop source {{ source }} profile {{ source_config.profile }}
-{% endfor %}
-{% endif %}
-{% endfor %}
-{% endif %}
-{% endmacro %}
diff --git a/data/templates/frr/staticd.frr.j2 b/data/templates/frr/staticd.frr.j2
index 68e3f21f9..c662b7650 100644
--- a/data/templates/frr/staticd.frr.j2
+++ b/data/templates/frr/staticd.frr.j2
@@ -1,12 +1,77 @@
-{% from 'frr/static_routes_macro.j2' import static_routes %}
+{# Common macro for recurroiing options for a static route #}
+{% macro route_options(route, interface_or_next_hop, config, table) %}
+{# j2lint: disable=jinja-statements-delimeter #}
+{% set ip_route = route ~ ' ' ~ interface_or_next_hop %}
+{% if config.interface is vyos_defined %}
+{% set ip_route = ip_route ~ ' ' ~ config.interface %}
+{% endif %}
+{% if config.tag is vyos_defined %}
+{% set ip_route = ip_route ~ ' tag ' ~ config.tag %}
+{% endif %}
+{% if config.distance is vyos_defined %}
+{% set ip_route = ip_route ~ ' ' ~ config.distance %}
+{% endif %}
+{% if config.bfd is vyos_defined %}
+{% set ip_route = ip_route ~ ' bfd' %}
+{% if config.bfd.multi_hop is vyos_defined %}
+{% set ip_route = ip_route ~ ' multi-hop' %}
+{% if config.bfd.source_address is vyos_defined %}
+{% set ip_route = ip_route ~ ' source ' ~ config.bfd.source_address %}
+{% endif %}
+{% endif %}
+{% if config.bfd.profile is vyos_defined %}
+{% set ip_route = ip_route ~ ' profile ' ~ config.bfd.profile %}
+{% endif %}
+{% endif %}
+{% if config.vrf is vyos_defined %}
+{% set ip_route = ip_route ~ ' nexthop-vrf ' ~ config.vrf %}
+{% endif %}
+{% if config.segments is vyos_defined %}
+{# Segments used in/for SRv6 #}
+{% set ip_route = ip_route ~ ' segments ' ~ config.segments %}
+{% endif %}
+{# Routing table to configure #}
+{% if table is vyos_defined %}
+{% set ip_route = ip_route ~ ' table ' ~ table %}
+{% endif %}
+{{ ip_route }}
+{%- endmacro -%}
+{# Build static IPv4/IPv6 route #}
+{% macro static_routes(ip_ipv6, prefix, prefix_config, table=None) %}
+{% set route = ip_ipv6 ~ 'route ' ~ prefix %}
+{% if prefix_config.interface is vyos_defined %}
+{% for interface, interface_config in prefix_config.interface.items() if interface_config.disable is not defined %}
+{{ route_options(route, interface, interface_config, table) }}
+{% endfor %}
+{% endif %}
+{% if prefix_config.next_hop is vyos_defined and prefix_config.next_hop is not none %}
+{% for next_hop, next_hop_config in prefix_config.next_hop.items() if next_hop_config.disable is not defined %}
+{{ route_options(route, next_hop, next_hop_config, table) }}
+{% endfor %}
+{% endif %}
+{% if prefix_config.dhcp_interface is vyos_defined %}
+{% for dhcp_interface in prefix_config.dhcp_interface %}
+{% set next_hop = dhcp_interface | get_dhcp_router %}
+{% if next_hop is vyos_defined %}
+{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ dhcp_interface }} {{ 'table ' ~ table if table is vyos_defined }}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% if prefix_config.blackhole is vyos_defined %}
+{{ route_options(route, 'blackhole', prefix_config.blackhole, table) }}
+{% elif prefix_config.reject is vyos_defined %}
+{{ route_options(route, 'reject', prefix_config.reject, table) }}
+{% endif %}
+{# j2lint: disable=jinja-statements-delimeter #}
+{%- endmacro -%}
!
-{% set ip_prefix = 'ip' %}
-{% set ipv6_prefix = 'ipv6' %}
+{% set ip_prefix = 'ip ' %}
+{% set ipv6_prefix = 'ipv6 ' %}
{% if vrf is vyos_defined %}
{# We need to add an additional whitespace in front of the prefix #}
{# when VRFs are in use, thus we use a variable for prefix handling #}
-{% set ip_prefix = ' ip' %}
-{% set ipv6_prefix = ' ipv6' %}
+{% set ip_prefix = ' ip ' %}
+{% set ipv6_prefix = ' ipv6 ' %}
vrf {{ vrf }}
{% endif %}
{# IPv4 routing #}
@@ -47,19 +112,33 @@ exit-vrf
{% for table_id, table_config in table.items() %}
{% if table_config.route is vyos_defined %}
{% for prefix, prefix_config in table_config.route.items() %}
-{{ static_routes('ip', prefix, prefix_config, table_id) }}
-{% endfor %}
+{{ static_routes('ip ', prefix, prefix_config, table_id) }}
+{# j2lint: disable=jinja-statements-delimeter #}
+{%- endfor %}
{% endif %}
!
{% if table_config.route6 is vyos_defined %}
{% for prefix, prefix_config in table_config.route6.items() %}
-{{ static_routes('ipv6', prefix, prefix_config, table_id) }}
-{% endfor %}
+{{ static_routes('ipv6 ', prefix, prefix_config, table_id) }}
+{# j2lint: disable=jinja-statements-delimeter #}
+{%- endfor %}
{% endif %}
!
{% endfor %}
{% endif %}
!
+{# Multicast route #}
+{% if multicast is vyos_defined %}
+{% set ip_prefix = 'ip m' %}
+{# IPv4 multicast routing #}
+{% if multicast.route is vyos_defined %}
+{% for prefix, prefix_config in multicast.route.items() %}
+{{ static_routes(ip_prefix, prefix, prefix_config) }}
+{# j2lint: disable=jinja-statements-delimeter #}
+{%- endfor %}
+{% endif %}
+{% endif %}
+!
{% if route_map is vyos_defined %}
ip protocol static route-map {{ route_map }}
!
diff --git a/data/templates/frr/zebra.vrf.route-map.frr.j2 b/data/templates/frr/zebra.vrf.route-map.frr.j2
index 8ebb82511..656b31deb 100644
--- a/data/templates/frr/zebra.vrf.route-map.frr.j2
+++ b/data/templates/frr/zebra.vrf.route-map.frr.j2
@@ -25,6 +25,6 @@ vrf {{ vrf }}
vni {{ vrf_config.vni }}
{% endif %}
exit-vrf
-{% endfor %}
!
+{% endfor %}
{% endif %}
diff --git a/interface-definitions/include/source-address-ipv4.xml.i b/interface-definitions/include/source-address-ipv4.xml.i
index 052678113..aa0b083c7 100644
--- a/interface-definitions/include/source-address-ipv4.xml.i
+++ b/interface-definitions/include/source-address-ipv4.xml.i
@@ -1,7 +1,7 @@
<!-- include start from source-address-ipv4.xml.i -->
<leafNode name="source-address">
<properties>
- <help>IPv4 source address used to initiate connection</help>
+ <help>IPv4 address used to initiate connection</help>
<completionHelp>
<script>${vyos_completion_dir}/list_local_ips.sh --ipv4</script>
</completionHelp>
diff --git a/interface-definitions/include/source-address-ipv6.xml.i b/interface-definitions/include/source-address-ipv6.xml.i
new file mode 100644
index 000000000..a27955b0c
--- /dev/null
+++ b/interface-definitions/include/source-address-ipv6.xml.i
@@ -0,0 +1,17 @@
+<!-- include start from source-address-ipv6.xml.i -->
+<leafNode name="source-address">
+ <properties>
+ <help>IPv6 address used to initiate connection</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_local_ips.sh --ipv6</script>
+ </completionHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 source address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/static/bfd-multi-hop.xml.i b/interface-definitions/include/static/bfd-multi-hop.xml.i
new file mode 100644
index 000000000..e53994191
--- /dev/null
+++ b/interface-definitions/include/static/bfd-multi-hop.xml.i
@@ -0,0 +1,8 @@
+<!-- include start from static/bfd-multi-hop.xml.i -->
+<leafNode name="multi-hop">
+ <properties>
+ <help>Enable BFD multi-hop session (requires source-address)</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/static/static-route-bfd.xml.i b/interface-definitions/include/static/static-route-bfd.xml.i
deleted file mode 100644
index d588b369f..000000000
--- a/interface-definitions/include/static/static-route-bfd.xml.i
+++ /dev/null
@@ -1,36 +0,0 @@
-<!-- include start from static/static-route-bfd.xml.i -->
-<node name="bfd">
- <properties>
- <help>BFD monitoring</help>
- </properties>
- <children>
- #include <include/bfd/profile.xml.i>
- <node name="multi-hop">
- <properties>
- <help>Use BFD multi hop session</help>
- </properties>
- <children>
- <tagNode name="source">
- <properties>
- <help>Use source for BFD session</help>
- <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>
- </properties>
- <children>
- #include <include/bfd/profile.xml.i>
- </children>
- </tagNode>
- </children>
- </node>
- </children>
-</node>
-<!-- include end -->
diff --git a/interface-definitions/include/static/static-route.xml.i b/interface-definitions/include/static/static-route.xml.i
index 51e45c6f7..fa1131118 100644
--- a/interface-definitions/include/static/static-route.xml.i
+++ b/interface-definitions/include/static/static-route.xml.i
@@ -51,10 +51,18 @@
#include <include/static/static-route-distance.xml.i>
#include <include/static/static-route-interface.xml.i>
#include <include/static/static-route-vrf.xml.i>
- #include <include/static/static-route-bfd.xml.i>
+ <node name="bfd">
+ <properties>
+ <help>BFD monitoring</help>
+ </properties>
+ <children>
+ #include <include/bfd/profile.xml.i>
+ #include <include/static/bfd-multi-hop.xml.i>
+ #include <include/source-address-ipv4.xml.i>
+ </children>
+ </node>
</children>
</tagNode>
</children>
</tagNode>
<!-- include end -->
-
diff --git a/interface-definitions/include/static/static-route6.xml.i b/interface-definitions/include/static/static-route6.xml.i
index 4468c8025..e75385dc7 100644
--- a/interface-definitions/include/static/static-route6.xml.i
+++ b/interface-definitions/include/static/static-route6.xml.i
@@ -48,11 +48,20 @@
</properties>
<children>
#include <include/generic-disable-node.xml.i>
- #include <include/static/static-route-bfd.xml.i>
#include <include/static/static-route-distance.xml.i>
#include <include/static/static-route-interface.xml.i>
#include <include/static/static-route-segments.xml.i>
#include <include/static/static-route-vrf.xml.i>
+ <node name="bfd">
+ <properties>
+ <help>BFD monitoring</help>
+ </properties>
+ <children>
+ #include <include/bfd/profile.xml.i>
+ #include <include/static/bfd-multi-hop.xml.i>
+ #include <include/source-address-ipv6.xml.i>
+ </children>
+ </node>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/protocols_static.xml.in b/interface-definitions/protocols_static.xml.in
index ca4ca2d74..407e56553 100644
--- a/interface-definitions/protocols_static.xml.in
+++ b/interface-definitions/protocols_static.xml.in
@@ -11,6 +11,62 @@
<priority>480</priority>
</properties>
<children>
+ <node name="multicast">
+ <properties>
+ <help>Multicast static route</help>
+ </properties>
+ <children>
+ <tagNode name="route">
+ <properties>
+ <help>Configure static unicast route into MRIB for multicast RPF lookup</help>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>Network</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-prefix"/>
+ </constraint>
+ </properties>
+ <children>
+ <tagNode name="next-hop">
+ <properties>
+ <help>Next-hop IPv4 router address</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Next-hop router address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/generic-disable-node.xml.i>
+ #include <include/static/static-route-distance.xml.i>
+ </children>
+ </tagNode>
+ <tagNode name="interface">
+ <properties>
+ <help>Next-hop IPv4 router interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Gateway interface name</description>
+ </valueHelp>
+ <constraint>
+ #include <include/constraint/interface-name.xml.i>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/generic-disable-node.xml.i>
+ #include <include/static/static-route-distance.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
#include <include/route-map.xml.i>
#include <include/static/static-route.xml.i>
#include <include/static/static-route6.xml.i>
diff --git a/interface-definitions/protocols_static_multicast.xml.in b/interface-definitions/protocols_static_multicast.xml.in
deleted file mode 100644
index caf95ed7c..000000000
--- a/interface-definitions/protocols_static_multicast.xml.in
+++ /dev/null
@@ -1,95 +0,0 @@
-<?xml version="1.0"?>
-<interfaceDefinition>
- <node name="protocols">
- <children>
- <node name="static">
- <children>
- <node name="multicast" owner="${vyos_conf_scripts_dir}/protocols_static_multicast.py">
- <properties>
- <help>Multicast static route</help>
- <priority>481</priority>
- </properties>
- <children>
- <tagNode name="route">
- <properties>
- <help>Configure static unicast route into MRIB for multicast RPF lookup</help>
- <valueHelp>
- <format>ipv4net</format>
- <description>Network</description>
- </valueHelp>
- <constraint>
- <validator name="ip-prefix"/>
- </constraint>
- </properties>
- <children>
- <tagNode name="next-hop">
- <properties>
- <help>Nexthop IPv4 address</help>
- <valueHelp>
- <format>ipv4</format>
- <description>Nexthop IPv4 address</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
- <children>
- <leafNode name="distance">
- <properties>
- <help>Distance value for this route</help>
- <valueHelp>
- <format>u32:1-255</format>
- <description>Distance for this route</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-255"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
- </tagNode>
- </children>
- </tagNode>
- <tagNode name="interface-route">
- <properties>
- <help>Multicast interface based route</help>
- <valueHelp>
- <format>ipv4net</format>
- <description>Network</description>
- </valueHelp>
- <constraint>
- <validator name="ip-prefix"/>
- </constraint>
- </properties>
- <children>
- <tagNode name="next-hop-interface">
- <properties>
- <help>Next-hop interface</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces</script>
- </completionHelp>
- </properties>
- <children>
- <leafNode name="distance">
- <properties>
- <help>Distance value for this route</help>
- <valueHelp>
- <format>u32:1-255</format>
- <description>Distance for this route</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-255"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
- </tagNode>
- </children>
- </tagNode>
- </children>
- </node>
- </children>
- </node>
- </children>
- </node>
-</interfaceDefinition>
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 5a353b110..88d131573 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -664,3 +664,468 @@ def get_accel_dict(config, base, chap_secrets, with_pki=False):
dict['authentication']['radius']['server'][server]['acct_port'] = '0'
return dict
+
+def get_frrender_dict(conf) -> dict:
+ from copy import deepcopy
+ from vyos.config import config_dict_merge
+ from vyos.frrender import frr_protocols
+
+ # Create an empty dictionary which will be filled down the code path and
+ # returned to the caller
+ dict = {}
+
+ def dict_helper_ospf_defaults(ospf, path):
+ # 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(path, key_mangling=('-', '_'),
+ get_first_key=True, 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.
+ if dict_search('default_information.originate', ospf) is None:
+ del default_values['default_information']
+ if 'mpls_te' not in ospf:
+ del default_values['mpls_te']
+ if 'graceful_restart' not in ospf:
+ del default_values['graceful_restart']
+ for area_num in default_values.get('area', []):
+ if dict_search(f'area.{area_num}.area_type.nssa', ospf) is None:
+ del default_values['area'][area_num]['area_type']['nssa']
+
+ for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']:
+ if dict_search(f'redistribute.{protocol}', ospf) is None:
+ del default_values['redistribute'][protocol]
+ if not bool(default_values['redistribute']):
+ del default_values['redistribute']
+
+ for interface in ospf.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 'hello_multiplier' in ospf['interface'][interface]:
+ del default_values['interface'][interface]['dead_interval']
+
+ ospf = config_dict_merge(default_values, ospf)
+ return ospf
+
+ def dict_helper_ospfv3_defaults(ospfv3, path):
+ # 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(path, key_mangling=('-', '_'),
+ get_first_key=True, 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.
+ if dict_search('default_information.originate', ospfv3) is None:
+ del default_values['default_information']
+ if 'graceful_restart' not in ospfv3:
+ del default_values['graceful_restart']
+
+ for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'ripng', 'static']:
+ if dict_search(f'redistribute.{protocol}', ospfv3) is None:
+ del default_values['redistribute'][protocol]
+ if not bool(default_values['redistribute']):
+ del default_values['redistribute']
+
+ default_values.pop('interface', {})
+
+ # merge in remaining default values
+ ospfv3 = config_dict_merge(default_values, ospfv3)
+ return ospfv3
+
+ def dict_helper_pim_defaults(pim, path):
+ # 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(path, key_mangling=('-', '_'),
+ get_first_key=True, recursive=True)
+
+ # We have to cleanup the default dict, as default values could enable features
+ # which are not explicitly enabled on the CLI.
+ for interface in pim.get('interface', []):
+ if 'igmp' not in pim['interface'][interface]:
+ del default_values['interface'][interface]['igmp']
+
+ pim = config_dict_merge(default_values, pim)
+ return pim
+
+ # Ethernet and bonding interfaces can participate in EVPN which is configured via FRR
+ tmp = {}
+ for if_type in ['ethernet', 'bonding']:
+ interface_path = ['interfaces', if_type]
+ if not conf.exists(interface_path):
+ continue
+ for interface in conf.list_nodes(interface_path):
+ evpn_path = interface_path + [interface, 'evpn']
+ if not conf.exists(evpn_path):
+ continue
+
+ evpn = conf.get_config_dict(evpn_path, key_mangling=('-', '_'))
+ tmp.update({interface : evpn})
+ # At least one participating EVPN interface found, add to result dict
+ if tmp: dict['interfaces'] = tmp
+
+ # Enable SNMP agentx support
+ # SNMP AgentX support cannot be disabled once enabled
+ if conf.exists(['service', 'snmp']):
+ dict['snmp'] = {}
+
+ # We will always need the policy key
+ dict['policy'] = conf.get_config_dict(['policy'], key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ # We need to check the CLI if the BABEL node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ babel_cli_path = ['protocols', 'babel']
+ if conf.exists(babel_cli_path):
+ babel = conf.get_config_dict(babel_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
+ dict.update({'babel' : babel})
+
+ # We need to check the CLI if the BFD node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ bfd_cli_path = ['protocols', 'bfd']
+ if conf.exists(bfd_cli_path):
+ bfd = conf.get_config_dict(bfd_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ with_recursive_defaults=True)
+ dict.update({'bfd' : bfd})
+
+ # We need to check the CLI if the BGP node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ bgp_cli_path = ['protocols', 'bgp']
+ if conf.exists(bgp_cli_path):
+ bgp = conf.get_config_dict(bgp_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ with_recursive_defaults=True)
+ bgp['dependent_vrfs'] = {}
+ dict.update({'bgp' : bgp})
+ elif conf.exists_effective(bgp_cli_path):
+ dict.update({'bgp' : {'deleted' : '', 'dependent_vrfs' : {}}})
+
+ # We need to check the CLI if the EIGRP node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ eigrp_cli_path = ['protocols', 'eigrp']
+ if conf.exists(eigrp_cli_path):
+ isis = conf.get_config_dict(eigrp_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ with_recursive_defaults=True)
+ dict.update({'eigrp' : isis})
+ elif conf.exists_effective(eigrp_cli_path):
+ dict.update({'eigrp' : {'deleted' : ''}})
+
+ # We need to check the CLI if the ISIS node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ isis_cli_path = ['protocols', 'isis']
+ if conf.exists(isis_cli_path):
+ isis = conf.get_config_dict(isis_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ with_recursive_defaults=True)
+ dict.update({'isis' : isis})
+ elif conf.exists_effective(isis_cli_path):
+ dict.update({'isis' : {'deleted' : ''}})
+
+ # We need to check the CLI if the MPLS node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ mpls_cli_path = ['protocols', 'mpls']
+ if conf.exists(mpls_cli_path):
+ mpls = conf.get_config_dict(mpls_cli_path, key_mangling=('-', '_'),
+ get_first_key=True)
+ dict.update({'mpls' : mpls})
+ elif conf.exists_effective(mpls_cli_path):
+ dict.update({'mpls' : {'deleted' : ''}})
+
+ # We need to check the CLI if the OPENFABRIC node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ openfabric_cli_path = ['protocols', 'openfabric']
+ if conf.exists(openfabric_cli_path):
+ openfabric = conf.get_config_dict(openfabric_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+ dict.update({'openfabric' : openfabric})
+ elif conf.exists_effective(openfabric_cli_path):
+ dict.update({'openfabric' : {'deleted' : ''}})
+
+ # We need to check the CLI if the OSPF node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ ospf_cli_path = ['protocols', 'ospf']
+ if conf.exists(ospf_cli_path):
+ ospf = conf.get_config_dict(ospf_cli_path, key_mangling=('-', '_'),
+ get_first_key=True)
+ ospf = dict_helper_ospf_defaults(ospf, ospf_cli_path)
+ dict.update({'ospf' : ospf})
+ elif conf.exists_effective(ospf_cli_path):
+ dict.update({'ospf' : {'deleted' : ''}})
+
+ # We need to check the CLI if the OSPFv3 node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ ospfv3_cli_path = ['protocols', 'ospfv3']
+ if conf.exists(ospfv3_cli_path):
+ ospfv3 = conf.get_config_dict(ospfv3_cli_path, key_mangling=('-', '_'),
+ get_first_key=True)
+ ospfv3 = dict_helper_ospfv3_defaults(ospfv3, ospfv3_cli_path)
+ dict.update({'ospfv3' : ospfv3})
+ elif conf.exists_effective(ospfv3_cli_path):
+ dict.update({'ospfv3' : {'deleted' : ''}})
+
+ # We need to check the CLI if the PIM node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ pim_cli_path = ['protocols', 'pim']
+ if conf.exists(pim_cli_path):
+ pim = conf.get_config_dict(pim_cli_path, key_mangling=('-', '_'),
+ get_first_key=True)
+ pim = dict_helper_pim_defaults(pim, pim_cli_path)
+ dict.update({'pim' : pim})
+ elif conf.exists_effective(pim_cli_path):
+ dict.update({'pim' : {'deleted' : ''}})
+
+ # We need to check the CLI if the PIM6 node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ pim6_cli_path = ['protocols', 'pim6']
+ if conf.exists(pim6_cli_path):
+ pim6 = conf.get_config_dict(pim6_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
+ dict.update({'pim6' : pim6})
+ elif conf.exists_effective(pim6_cli_path):
+ dict.update({'pim6' : {'deleted' : ''}})
+
+ # We need to check the CLI if the RIP node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ rip_cli_path = ['protocols', 'rip']
+ if conf.exists(rip_cli_path):
+ rip = conf.get_config_dict(rip_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
+ dict.update({'rip' : rip})
+ elif conf.exists_effective(rip_cli_path):
+ dict.update({'rip' : {'deleted' : ''}})
+
+ # We need to check the CLI if the RIPng node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ ripng_cli_path = ['protocols', 'ripng']
+ if conf.exists(ripng_cli_path):
+ ripng = conf.get_config_dict(ripng_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
+ dict.update({'ripng' : ripng})
+ elif conf.exists_effective(ripng_cli_path):
+ dict.update({'ripng' : {'deleted' : ''}})
+
+ # We need to check the CLI if the RPKI node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ rpki_cli_path = ['protocols', 'rpki']
+ if conf.exists(rpki_cli_path):
+ rpki = conf.get_config_dict(rpki_cli_path, key_mangling=('-', '_'),
+ get_first_key=True, with_pki=True,
+ with_recursive_defaults=True)
+ dict.update({'rpki' : rpki})
+ elif conf.exists_effective(rpki_cli_path):
+ dict.update({'rpki' : {'deleted' : ''}})
+
+ # We need to check the CLI if the Segment Routing node is present and thus load in
+ # all the default values present on the CLI - that's why we have if conf.exists()
+ sr_cli_path = ['protocols', 'segment-routing']
+ if conf.exists(sr_cli_path):
+ sr = conf.get_config_dict(sr_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ with_recursive_defaults=True)
+ dict.update({'segment_routing' : sr})
+ elif conf.exists_effective(sr_cli_path):
+ dict.update({'segment_routing' : {'deleted' : ''}})
+
+ # We need to check the CLI if the static node is present and thus load in
+ # all the default values present on the CLI - that's why we have if conf.exists()
+ static_cli_path = ['protocols', 'static']
+ if conf.exists(static_cli_path):
+ static = conf.get_config_dict(static_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ # T3680 - get a list of all interfaces currently configured to use DHCP
+ tmp = get_dhcp_interfaces(conf)
+ if tmp: static.update({'dhcp' : tmp})
+ tmp = get_pppoe_interfaces(conf)
+ if tmp: static.update({'pppoe' : tmp})
+
+ dict.update({'static' : static})
+ elif conf.exists_effective(static_cli_path):
+ dict.update({'static' : {'deleted' : ''}})
+
+ # keep a re-usable list of dependent VRFs
+ dependent_vrfs_default = {}
+ if 'bgp' in dict:
+ dependent_vrfs_default = deepcopy(dict['bgp'])
+ # we do not need to nest the 'dependent_vrfs' key - simply remove it
+ if 'dependent_vrfs' in dependent_vrfs_default:
+ del dependent_vrfs_default['dependent_vrfs']
+
+ vrf_cli_path = ['vrf', 'name']
+ if conf.exists(vrf_cli_path):
+ vrf = conf.get_config_dict(vrf_cli_path, key_mangling=('-', '_'),
+ get_first_key=False,
+ no_tag_node_value_mangle=True)
+ # We do not have any VRF related default values on the CLI. The defaults will only
+ # come into place under the protocols tree, thus we can safely merge them with the
+ # appropriate routing protocols
+ for vrf_name, vrf_config in vrf['name'].items():
+ bgp_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'bgp']
+ if 'bgp' in vrf_config.get('protocols', []):
+ # 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(bgp_vrf_path, key_mangling=('-', '_'),
+ get_first_key=True, recursive=True)
+
+ # merge in remaining default values
+ vrf_config['protocols']['bgp'] = config_dict_merge(default_values,
+ vrf_config['protocols']['bgp'])
+
+ # Add this BGP VRF instance as dependency into the default VRF
+ if 'bgp' in dict:
+ dict['bgp']['dependent_vrfs'].update({vrf_name : deepcopy(vrf_config)})
+
+ vrf_config['protocols']['bgp']['dependent_vrfs'] = conf.get_config_dict(
+ vrf_cli_path, key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ # We can safely delete ourself from the dependent VRF list
+ if vrf_name in vrf_config['protocols']['bgp']['dependent_vrfs']:
+ del vrf_config['protocols']['bgp']['dependent_vrfs'][vrf_name]
+
+ # Add dependency on possible existing default VRF to this VRF
+ if 'bgp' in dict:
+ vrf_config['protocols']['bgp']['dependent_vrfs'].update({'default': {'protocols': {
+ 'bgp': dependent_vrfs_default}}})
+ elif conf.exists_effective(bgp_vrf_path):
+ # Add this BGP VRF instance as dependency into the default VRF
+ tmp = {'deleted' : '', 'dependent_vrfs': deepcopy(vrf['name'])}
+ # We can safely delete ourself from the dependent VRF list
+ if vrf_name in tmp['dependent_vrfs']:
+ del tmp['dependent_vrfs'][vrf_name]
+
+ # Add dependency on possible existing default VRF to this VRF
+ if 'bgp' in dict:
+ tmp['dependent_vrfs'].update({'default': {'protocols': {
+ 'bgp': dependent_vrfs_default}}})
+
+ if 'bgp' in dict:
+ dict['bgp']['dependent_vrfs'].update({vrf_name : {'protocols': tmp} })
+ vrf['name'][vrf_name]['protocols'].update({'bgp' : tmp})
+
+ # We need to check the CLI if the EIGRP node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ eigrp_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'eigrp']
+ if 'eigrp' in vrf_config.get('protocols', []):
+ eigrp = conf.get_config_dict(eigrp_vrf_path, key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+ vrf['name'][vrf_name]['protocols'].update({'eigrp' : isis})
+ elif conf.exists_effective(eigrp_vrf_path):
+ vrf['name'][vrf_name]['protocols'].update({'eigrp' : {'deleted' : ''}})
+
+ # We need to check the CLI if the ISIS node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ isis_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'isis']
+ if 'isis' in vrf_config.get('protocols', []):
+ isis = conf.get_config_dict(isis_vrf_path, key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True, with_recursive_defaults=True)
+ vrf['name'][vrf_name]['protocols'].update({'isis' : isis})
+ elif conf.exists_effective(isis_vrf_path):
+ vrf['name'][vrf_name]['protocols'].update({'isis' : {'deleted' : ''}})
+
+ # We need to check the CLI if the OSPF node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ ospf_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'ospf']
+ if 'ospf' in vrf_config.get('protocols', []):
+ ospf = conf.get_config_dict(ospf_vrf_path, key_mangling=('-', '_'), get_first_key=True)
+ ospf = dict_helper_ospf_defaults(vrf_config['protocols']['ospf'], ospf_vrf_path)
+ vrf['name'][vrf_name]['protocols'].update({'ospf' : ospf})
+ elif conf.exists_effective(ospf_vrf_path):
+ vrf['name'][vrf_name]['protocols'].update({'ospf' : {'deleted' : ''}})
+
+ # We need to check the CLI if the OSPFv3 node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ ospfv3_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'ospfv3']
+ if 'ospfv3' in vrf_config.get('protocols', []):
+ ospfv3 = conf.get_config_dict(ospfv3_vrf_path, key_mangling=('-', '_'), get_first_key=True)
+ ospfv3 = dict_helper_ospfv3_defaults(vrf_config['protocols']['ospfv3'], ospfv3_vrf_path)
+ vrf['name'][vrf_name]['protocols'].update({'ospfv3' : ospfv3})
+ elif conf.exists_effective(ospfv3_vrf_path):
+ vrf['name'][vrf_name]['protocols'].update({'ospfv3' : {'deleted' : ''}})
+
+ # We need to check the CLI if the static node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ static_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'static']
+ if 'static' in vrf_config.get('protocols', []):
+ static = conf.get_config_dict(static_vrf_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+ # T3680 - get a list of all interfaces currently configured to use DHCP
+ tmp = get_dhcp_interfaces(conf, vrf_name)
+ if tmp: static.update({'dhcp' : tmp})
+ tmp = get_pppoe_interfaces(conf, vrf_name)
+ if tmp: static.update({'pppoe' : tmp})
+
+ vrf['name'][vrf_name]['protocols'].update({'static': static})
+ elif conf.exists_effective(static_vrf_path):
+ vrf['name'][vrf_name]['protocols'].update({'static': {'deleted' : ''}})
+
+ vrf_vni_path = ['vrf', 'name', vrf_name, 'vni']
+ if conf.exists_effective(vrf_vni_path):
+ vrf_config.update({'vni': conf.return_effective_value(vrf_vni_path)})
+
+ dict.update({'vrf' : vrf})
+ elif conf.exists_effective(vrf_cli_path):
+ effective_vrf = conf.get_config_dict(vrf_cli_path, key_mangling=('-', '_'),
+ get_first_key=False,
+ no_tag_node_value_mangle=True,
+ effective=True)
+ vrf = {'name' : {}}
+ for vrf_name, vrf_config in effective_vrf.get('name', {}).items():
+ vrf['name'].update({vrf_name : {}})
+ for protocol in frr_protocols:
+ if protocol in vrf_config.get('protocols', []):
+ # Create initial protocols key if not present
+ if 'protocols' not in vrf['name'][vrf_name]:
+ vrf['name'][vrf_name].update({'protocols' : {}})
+ # All routing protocols are deleted when we pass this point
+ tmp = {'deleted' : ''}
+
+ # Special treatment for BGP routing protocol
+ if protocol == 'bgp':
+ tmp['dependent_vrfs'] = {}
+ if 'name' in vrf:
+ tmp['dependent_vrfs'] = conf.get_config_dict(
+ vrf_cli_path, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True,
+ effective=True)
+ # Add dependency on possible existing default VRF to this VRF
+ if 'bgp' in dict:
+ tmp['dependent_vrfs'].update({'default': {'protocols': {
+ 'bgp': dependent_vrfs_default}}})
+ # We can safely delete ourself from the dependent VRF list
+ if vrf_name in tmp['dependent_vrfs']:
+ del tmp['dependent_vrfs'][vrf_name]
+
+ # Update VRF related dict
+ vrf['name'][vrf_name]['protocols'].update({protocol : tmp})
+
+ dict.update({'vrf' : vrf})
+
+ print('======== < > ==========')
+ import pprint
+ pprint.pprint(dict)
+ print('======== < > ==========')
+ return dict
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index 92996f2ee..4450dc16b 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -420,7 +420,7 @@ def verify_common_route_maps(config):
continue
tmp = config[route_map]
# Check if the specified route-map exists, if not error out
- if dict_search(f'policy.route-map.{tmp}', config) == None:
+ if dict_search(f'policy.route_map.{tmp}', config) == None:
raise ConfigError(f'Specified route-map "{tmp}" does not exist!')
if 'redistribute' in config:
@@ -434,7 +434,7 @@ def verify_route_map(route_map_name, config):
recurring validation if a specified route-map exists!
"""
# Check if the specified route-map exists, if not error out
- if dict_search(f'policy.route-map.{route_map_name}', config) == None:
+ if dict_search(f'policy.route_map.{route_map_name}', config) == None:
raise ConfigError(f'Specified route-map "{route_map_name}" does not exist!')
def verify_prefix_list(prefix_list, config, version=''):
@@ -443,7 +443,7 @@ def verify_prefix_list(prefix_list, config, version=''):
recurring validation if a specified prefix-list exists!
"""
# Check if the specified prefix-list exists, if not error out
- if dict_search(f'policy.prefix-list{version}.{prefix_list}', config) == None:
+ if dict_search(f'policy.prefix_list{version}.{prefix_list}', config) == None:
raise ConfigError(f'Specified prefix-list{version} "{prefix_list}" does not exist!')
def verify_access_list(access_list, config, version=''):
@@ -452,7 +452,7 @@ def verify_access_list(access_list, config, version=''):
recurring validation if a specified prefix-list exists!
"""
# Check if the specified ACL exists, if not error out
- if dict_search(f'policy.access-list{version}.{access_list}', config) == None:
+ if dict_search(f'policy.access_list{version}.{access_list}', config) == None:
raise ConfigError(f'Specified access-list{version} "{access_list}" does not exist!')
def verify_pki_certificate(config: dict, cert_name: str, no_password_protected: bool=False):
@@ -537,3 +537,10 @@ def verify_eapol(config: dict):
if 'ca_certificate' in config['eapol']:
for ca_cert in config['eapol']['ca_certificate']:
verify_pki_ca_certificate(config, ca_cert)
+
+def has_frr_protocol_in_dict(config_dict: dict, protocol: str, vrf: str=None) -> bool:
+ if vrf and protocol in (dict_search(f'vrf.name.{vrf}.protocols', config_dict) or []):
+ return True
+ if config_dict and protocol in config_dict:
+ return True
+ return False
diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py
new file mode 100644
index 000000000..015596a8f
--- /dev/null
+++ b/python/vyos/frrender.py
@@ -0,0 +1,156 @@
+# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Library used to interface with FRRs mgmtd introduced in version 10.0
+"""
+
+import os
+
+from vyos.utils.file import write_file
+from vyos.utils.process import rc_cmd
+from vyos.template import render_to_string
+from vyos import ConfigError
+
+DEBUG_ON = os.path.exists('/tmp/vyos.frr.debug')
+DEBUG_ON = True
+
+def debug(message):
+ if not DEBUG_ON:
+ return
+ print(message)
+
+pim_daemon = 'pimd'
+
+frr_protocols = ['babel', 'bfd', 'bgp', 'eigrp', 'isis', 'mpls', 'nhrp',
+ 'openfabric', 'ospf', 'ospfv3', 'pim', 'pim6', 'rip',
+ 'ripng', 'rpki', 'segment_routing', 'static']
+
+class FRRender:
+ def __init__(self):
+ self._frr_conf = '/run/frr/config/frr.conf'
+
+ def generate(self, config):
+ if not isinstance(config, dict):
+ raise ValueError('config must be of type dict')
+
+ def inline_helper(config_dict) -> str:
+ output = '!\n'
+ if 'babel' in config_dict and 'deleted' not in config_dict['babel']:
+ output += render_to_string('frr/babeld.frr.j2', config_dict['babel'])
+ output += '\n'
+ if 'bfd' in config_dict and 'deleted' not in config_dict['bfd']:
+ output += render_to_string('frr/bfdd.frr.j2', config_dict['bfd'])
+ output += '\n'
+ if 'bgp' in config_dict and 'deleted' not in config_dict['bgp']:
+ output += render_to_string('frr/bgpd.frr.j2', config_dict['bgp'])
+ output += '\n'
+ if 'eigrp' in config_dict and 'deleted' not in config_dict['eigrp']:
+ output += render_to_string('frr/eigrpd.frr.j2', config_dict['eigrp'])
+ output += '\n'
+ if 'isis' in config_dict and 'deleted' not in config_dict['isis']:
+ output += render_to_string('frr/isisd.frr.j2', config_dict['isis'])
+ output += '\n'
+ if 'mpls' in config_dict and 'deleted' not in config_dict['mpls']:
+ output += render_to_string('frr/ldpd.frr.j2', config_dict['mpls'])
+ output += '\n'
+ if 'openfabric' in config_dict and 'deleted' not in config_dict['openfabric']:
+ output += render_to_string('frr/fabricd.frr.j2', config_dict['openfabric'])
+ output += '\n'
+ if 'ospf' in config_dict and 'deleted' not in config_dict['ospf']:
+ output += render_to_string('frr/ospfd.frr.j2', config_dict['ospf'])
+ output += '\n'
+ if 'ospfv3' in config_dict and 'deleted' not in config_dict['ospfv3']:
+ output += render_to_string('frr/ospf6d.frr.j2', config_dict['ospfv3'])
+ output += '\n'
+ if 'pim' in config_dict and 'deleted' not in config_dict['pim']:
+ output += render_to_string('frr/pimd.frr.j2', config_dict['pim'])
+ output += '\n'
+ if 'pim6' in config_dict and 'deleted' not in config_dict['pim6']:
+ output += render_to_string('frr/pim6d.frr.j2', config_dict['pim6'])
+ output += '\n'
+ if 'policy' in config_dict and len(config_dict['policy']) > 0:
+ output += render_to_string('frr/policy.frr.j2', config_dict['policy'])
+ output += '\n'
+ if 'rip' in config_dict and 'deleted' not in config_dict['rip']:
+ output += render_to_string('frr/ripd.frr.j2', config_dict['rip'])
+ output += '\n'
+ if 'ripng' in config_dict and 'deleted' not in config_dict['ripng']:
+ output += render_to_string('frr/ripngd.frr.j2', config_dict['ripng'])
+ output += '\n'
+ if 'rpki' in config_dict and 'deleted' not in config_dict['rpki']:
+ output += render_to_string('frr/rpki.frr.j2', config_dict['rpki'])
+ output += '\n'
+ if 'segment_routing' in config_dict and 'deleted' not in config_dict['segment_routing']:
+ output += render_to_string('frr/zebra.segment_routing.frr.j2', config_dict['segment_routing'])
+ output += '\n'
+ if 'static' in config_dict and 'deleted' not in config_dict['static']:
+ output += render_to_string('frr/staticd.frr.j2', config_dict['static'])
+ output += '\n'
+ return output
+
+ debug('======< RENDERING CONFIG >======')
+ # we can not reload an empty file, thus we always embed the marker
+ output = '!\n'
+ # Enable SNMP agentx support
+ # SNMP AgentX support cannot be disabled once enabled
+ if 'snmp' in config:
+ output += 'agentx\n'
+ # Add routing protocols in global VRF
+ output += inline_helper(config)
+ # Interface configuration for EVPN is not VRF related
+ if 'interfaces' in config:
+ output += render_to_string('frr/evpn.mh.frr.j2', {'interfaces' : config['interfaces']})
+ output += '\n'
+
+ if 'vrf' in config and 'name' in config['vrf']:
+ output += render_to_string('frr/zebra.vrf.route-map.frr.j2', config['vrf']) + '\n'
+ for vrf, vrf_config in config['vrf']['name'].items():
+ if 'protocols' not in vrf_config:
+ continue
+ for protocol in vrf_config['protocols']:
+ vrf_config['protocols'][protocol]['vrf'] = vrf
+
+ output += inline_helper(vrf_config['protocols'])
+
+ debug(output)
+ debug('======< RENDERING CONFIG COMPLETE >======')
+ write_file(self._frr_conf, output)
+ if DEBUG_ON: write_file('/tmp/frr.conf.debug', output)
+
+ def apply(self):
+ count = 0
+ count_max = 5
+ emsg = ''
+ while count < count_max:
+ count += 1
+ print('FRR: Reloading configuration', count)
+
+ cmdline = '/usr/lib/frr/frr-reload.py --reload'
+ if DEBUG_ON:
+ cmdline += ' --debug'
+ rc, emsg = rc_cmd(f'{cmdline} {self._frr_conf}')
+ if rc != 0:
+ debug('FRR configuration reload failed, retrying')
+ continue
+ debug(emsg)
+ debug('======< DONE APPLYING CONFIG >======')
+ break
+ if count >= count_max:
+ raise ConfigError(emsg)
+
+ def save_configuration():
+ """ T3217: Save FRR configuration to /run/frr/config/frr.conf """
+ return cmd('/usr/bin/vtysh -n --writeconfig')
diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py
index a0c6ab055..7ea1b610e 100755
--- a/smoketest/scripts/cli/test_policy.py
+++ b/smoketest/scripts/cli/test_policy.py
@@ -1945,7 +1945,7 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
local_preference = base_local_preference
table = base_table
for route_map in route_maps:
- config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='')
+ config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='', endsection='^exit')
self.assertIn(f' set local-preference {local_preference}', config)
self.assertIn(f' set table {table}', config)
local_preference += 20
@@ -1958,7 +1958,7 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
local_preference = base_local_preference
for route_map in route_maps:
- config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='')
+ config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='', endsection='^exit')
self.assertIn(f' set local-preference {local_preference}', config)
local_preference += 20
@@ -1972,7 +1972,7 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
for route_map in route_maps:
- config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='')
+ config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='', endsection='^exit')
self.assertIn(f' set as-path prepend {prepend}', config)
for route_map in route_maps:
@@ -1981,7 +1981,7 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
for route_map in route_maps:
- config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='')
+ config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='', endsection='^exit')
self.assertNotIn(f' set', config)
def sort_ip(output):
diff --git a/smoketest/scripts/cli/test_protocols_bfd.py b/smoketest/scripts/cli/test_protocols_bfd.py
index 716d0a806..9f178c821 100755
--- a/smoketest/scripts/cli/test_protocols_bfd.py
+++ b/smoketest/scripts/cli/test_protocols_bfd.py
@@ -22,6 +22,7 @@ from vyos.utils.process import process_named_running
PROCESS_NAME = 'bfdd'
base_path = ['protocols', 'bfd']
+frr_endsection = '^ exit'
dum_if = 'dum1001'
vrf_name = 'red'
@@ -130,7 +131,7 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR bgpd configuration
- frrconfig = self.getFRRconfig('bfd', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('bfd', endsection='^exit', daemon=PROCESS_NAME)
for peer, peer_config in peers.items():
tmp = f'peer {peer}'
if 'multihop' in peer_config:
@@ -143,8 +144,8 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase):
tmp += f' vrf {peer_config["vrf"]}'
self.assertIn(tmp, frrconfig)
- peerconfig = self.getFRRconfig(f' peer {peer}', end='', daemon=PROCESS_NAME)
-
+ peerconfig = self.getFRRconfig(f' peer {peer}', end='', endsection=frr_endsection,
+ daemon=PROCESS_NAME)
if 'echo_mode' in peer_config:
self.assertIn(f'echo-mode', peerconfig)
if 'intv_echo' in peer_config:
@@ -206,7 +207,7 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase):
# Verify FRR bgpd configuration
for profile, profile_config in profiles.items():
- config = self.getFRRconfig(f' profile {profile}', endsection='^ !')
+ config = self.getFRRconfig(f' profile {profile}', endsection=frr_endsection)
if 'echo_mode' in profile_config:
self.assertIn(f' echo-mode', config)
if 'intv_echo' in profile_config:
@@ -228,7 +229,8 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase):
self.assertNotIn(f'shutdown', config)
for peer, peer_config in peers.items():
- peerconfig = self.getFRRconfig(f' peer {peer}', end='', daemon=PROCESS_NAME)
+ peerconfig = self.getFRRconfig(f' peer {peer}', end='',
+ endsection=frr_endsection, daemon=PROCESS_NAME)
if 'profile' in peer_config:
self.assertIn(f' profile {peer_config["profile"]}', peerconfig)
diff --git a/smoketest/scripts/cli/test_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py
index f676e2a52..980bc6e7f 100755
--- a/smoketest/scripts/cli/test_protocols_static.py
+++ b/smoketest/scripts/cli/test_protocols_static.py
@@ -33,7 +33,11 @@ routes = {
'192.0.2.110' : { 'distance' : '110', 'interface' : 'eth0' },
'192.0.2.120' : { 'distance' : '120', 'disable' : '' },
'192.0.2.130' : { 'bfd' : '' },
- '192.0.2.140' : { 'bfd_source' : '192.0.2.10' },
+ '192.0.2.131' : { 'bfd' : '',
+ 'bfd_profile' : 'vyos1' },
+ '192.0.2.140' : { 'bfd' : '',
+ 'bfd_source' : '192.0.2.10',
+ 'bfd_profile' : 'vyos2' },
},
'interface' : {
'eth0' : { 'distance' : '130' },
@@ -114,6 +118,45 @@ routes = {
},
}
+multicast_routes = {
+ '224.0.0.0/24' : {
+ 'next_hop' : {
+ '224.203.0.1' : { },
+ '224.203.0.2' : { 'distance' : '110'},
+ },
+ },
+ '224.1.0.0/24' : {
+ 'next_hop' : {
+ '224.205.0.1' : { 'disable' : {} },
+ '224.205.0.2' : { 'distance' : '110'},
+ },
+ },
+ '224.2.0.0/24' : {
+ 'next_hop' : {
+ '1.2.3.0' : { },
+ '1.2.3.1' : { 'distance' : '110'},
+ },
+ },
+ '224.10.0.0/24' : {
+ 'interface' : {
+ 'eth1' : { 'disable' : {} },
+ 'eth2' : { 'distance' : '110'},
+ },
+ },
+ '224.11.0.0/24' : {
+ 'interface' : {
+ 'eth0' : { },
+ 'eth1' : { 'distance' : '10'},
+ },
+ },
+ '224.12.0.0/24' : {
+ 'interface' : {
+ 'eth0' : { },
+ 'eth1' : { 'distance' : '200'},
+ },
+ },
+}
+
tables = ['80', '81', '82']
class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
@@ -138,7 +181,6 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
self.assertFalse(v6route)
def test_01_static(self):
- bfd_profile = 'vyos-test'
for route, route_config in routes.items():
route_type = 'route'
if is_ipv6(route):
@@ -156,9 +198,12 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
if 'vrf' in next_hop_config:
self.cli_set(base + ['next-hop', next_hop, 'vrf', next_hop_config['vrf']])
if 'bfd' in next_hop_config:
- self.cli_set(base + ['next-hop', next_hop, 'bfd', 'profile', bfd_profile ])
- if 'bfd_source' in next_hop_config:
- self.cli_set(base + ['next-hop', next_hop, 'bfd', 'multi-hop', 'source', next_hop_config['bfd_source'], 'profile', bfd_profile])
+ self.cli_set(base + ['next-hop', next_hop, 'bfd'])
+ if 'bfd_profile' in next_hop_config:
+ self.cli_set(base + ['next-hop', next_hop, 'bfd', 'profile', next_hop_config['bfd_profile']])
+ if 'bfd_source' in next_hop_config:
+ self.cli_set(base + ['next-hop', next_hop, 'bfd', 'multi-hop'])
+ self.cli_set(base + ['next-hop', next_hop, 'bfd', 'source-address', next_hop_config['bfd_source']])
if 'segments' in next_hop_config:
self.cli_set(base + ['next-hop', next_hop, 'segments', next_hop_config['segments']])
@@ -217,9 +262,11 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
if 'vrf' in next_hop_config:
tmp += ' nexthop-vrf ' + next_hop_config['vrf']
if 'bfd' in next_hop_config:
- tmp += ' bfd profile ' + bfd_profile
- if 'bfd_source' in next_hop_config:
- tmp += ' bfd multi-hop source ' + next_hop_config['bfd_source'] + ' profile ' + bfd_profile
+ tmp += ' bfd'
+ if 'bfd_source' in next_hop_config:
+ tmp += ' multi-hop source ' + next_hop_config['bfd_source']
+ if 'bfd_profile' in next_hop_config:
+ tmp += ' profile ' + next_hop_config['bfd_profile']
if 'segments' in next_hop_config:
tmp += ' segments ' + next_hop_config['segments']
@@ -426,7 +473,7 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
self.assertEqual(tmp['linkinfo']['info_kind'], 'vrf')
# Verify FRR bgpd configuration
- frrconfig = self.getFRRconfig(f'vrf {vrf}')
+ frrconfig = self.getFRRconfig(f'vrf {vrf}', endsection='^exit-vrf')
self.assertIn(f'vrf {vrf}', frrconfig)
# Verify routes
@@ -478,5 +525,48 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
self.assertIn(tmp, frrconfig)
+ def test_04_static_multicast(self):
+ for route, route_config in multicast_routes.items():
+ if 'next_hop' in route_config:
+ base = base_path + ['multicast', 'route', route]
+ for next_hop, next_hop_config in route_config['next_hop'].items():
+ self.cli_set(base + ['next-hop', next_hop])
+ if 'distance' in next_hop_config:
+ self.cli_set(base + ['next-hop', next_hop, 'distance', next_hop_config['distance']])
+ if 'disable' in next_hop_config:
+ self.cli_set(base + ['next-hop', next_hop, 'disable'])
+
+ if 'interface' in route_config:
+ base = base_path + ['multicast', 'route', route]
+ for next_hop, next_hop_config in route_config['interface'].items():
+ self.cli_set(base + ['interface', next_hop])
+ if 'distance' in next_hop_config:
+ self.cli_set(base + ['interface', next_hop, 'distance', next_hop_config['distance']])
+
+ self.cli_commit()
+
+ # Verify FRR configuration
+ frrconfig = self.getFRRconfig('ip mroute', end='')
+ for route, route_config in multicast_routes.items():
+ if 'next_hop' in route_config:
+ for next_hop, next_hop_config in route_config['next_hop'].items():
+ tmp = f'ip mroute {route} {next_hop}'
+ if 'distance' in next_hop_config:
+ tmp += ' ' + next_hop_config['distance']
+ if 'disable' in next_hop_config:
+ self.assertNotIn(tmp, frrconfig)
+ else:
+ self.assertIn(tmp, frrconfig)
+
+ if 'next_hop_interface' in route_config:
+ for next_hop, next_hop_config in route_config['next_hop_interface'].items():
+ tmp = f'ip mroute {route} {next_hop}'
+ if 'distance' in next_hop_config:
+ tmp += ' ' + next_hop_config['distance']
+ if 'disable' in next_hop_config:
+ self.assertNotIn(tmp, frrconfig)
+ else:
+ self.assertIn(tmp, frrconfig)
+
if __name__ == '__main__':
- unittest.main(verbosity=2, failfast=True)
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_protocols_static_multicast.py b/smoketest/scripts/cli/test_protocols_static_multicast.py
deleted file mode 100755
index 9fdda236f..000000000
--- a/smoketest/scripts/cli/test_protocols_static_multicast.py
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2024 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
-
-
-base_path = ['protocols', 'static', 'multicast']
-
-
-class TestProtocolsStaticMulticast(VyOSUnitTestSHIM.TestCase):
-
- def tearDown(self):
- self.cli_delete(base_path)
- self.cli_commit()
-
- mroute = self.getFRRconfig('ip mroute', end='')
- self.assertFalse(mroute)
-
- def test_01_static_multicast(self):
-
- self.cli_set(base_path + ['route', '224.202.0.0/24', 'next-hop', '224.203.0.1'])
- self.cli_set(base_path + ['interface-route', '224.203.0.0/24', 'next-hop-interface', 'eth0'])
-
- self.cli_commit()
-
- # Verify FRR bgpd configuration
- frrconfig = self.getFRRconfig('ip mroute', end='')
-
- self.assertIn('ip mroute 224.202.0.0/24 224.203.0.1', frrconfig)
- self.assertIn('ip mroute 224.203.0.0/24 eth0', frrconfig)
-
-
-if __name__ == '__main__':
- unittest.main(verbosity=2)
diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py
index 633fb797c..adea8fc63 100755
--- a/src/conf_mode/interfaces_bonding.py
+++ b/src/conf_mode/interfaces_bonding.py
@@ -17,6 +17,7 @@
from sys import exit
from vyos.config import Config
+from vyos.configdict import get_frrender_dict
from vyos.configdict import get_interface_dict
from vyos.configdict import is_node_changed
from vyos.configdict import leaf_node_changed
@@ -30,10 +31,10 @@ from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_vlan_config
from vyos.configverify import verify_vrf
+from vyos.frrender import FRRender
from vyos.ifconfig import BondIf
from vyos.ifconfig.ethernet import EthernetIf
from vyos.ifconfig import Section
-from vyos.template import render_to_string
from vyos.utils.assertion import assert_mac
from vyos.utils.dict import dict_search
from vyos.utils.dict import dict_to_paths_values
@@ -42,9 +43,9 @@ from vyos.configdict import has_address_configured
from vyos.configdict import has_vrf_configured
from vyos.configdep import set_dependents, call_dependents
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
+frrender = FRRender()
def get_bond_mode(mode):
if mode == 'round-robin':
@@ -87,10 +88,13 @@ def get_config(config=None):
bond['mode'] = get_bond_mode(bond['mode'])
tmp = is_node_changed(conf, base + [ifname, 'mode'])
- if tmp: bond['shutdown_required'] = {}
+ if tmp: bond.update({'shutdown_required' : {}})
tmp = is_node_changed(conf, base + [ifname, 'lacp-rate'])
- if tmp: bond['shutdown_required'] = {}
+ if tmp: bond.update({'shutdown_required' : {}})
+
+ tmp = is_node_changed(conf, base + [ifname, 'evpn'])
+ if tmp: bond.update({'frrender' : get_frrender_dict(conf)})
# determine which members have been removed
interfaces_removed = leaf_node_changed(conf, base + [ifname, 'member', 'interface'])
@@ -260,16 +264,16 @@ def verify(bond):
return None
def generate(bond):
- bond['frr_zebra_config'] = ''
- if 'deleted' not in bond:
- bond['frr_zebra_config'] = render_to_string('frr/evpn.mh.frr.j2', bond)
+ if 'frrender' in bond:
+ frrender.generate(bond['frrender'])
return None
def apply(bond):
- ifname = bond['ifname']
- b = BondIf(ifname)
+ if 'frrender' in bond:
+ frrender.apply()
+
+ b = BondIf(bond['ifname'])
if 'deleted' in bond:
- # delete interface
b.remove()
else:
b.update(bond)
@@ -281,16 +285,6 @@ def apply(bond):
raise ConfigError('Error in updating ethernet interface '
'after deleting it from bond')
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
-
- # The route-map used for the FIB (zebra) is part of the zebra daemon
- frr_cfg.load_configuration(frr.mgmt_daemon)
- frr_cfg.modify_section(f'^interface {ifname}', stop_pattern='^exit', remove_stop_mark=True)
- if 'frr_zebra_config' in bond:
- frr_cfg.add_before(frr.default_add_before, bond['frr_zebra_config'])
- frr_cfg.commit_configuration()
-
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py
index edbbb00c9..6a035e9e9 100755
--- a/src/conf_mode/interfaces_ethernet.py
+++ b/src/conf_mode/interfaces_ethernet.py
@@ -20,6 +20,7 @@ from sys import exit
from vyos.base import Warning
from vyos.config import Config
+from vyos.configdict import get_frrender_dict
from vyos.configdict import get_interface_dict
from vyos.configdict import is_node_changed
from vyos.configverify import verify_address
@@ -33,17 +34,17 @@ from vyos.configverify import verify_vrf
from vyos.configverify import verify_bond_bridge_member
from vyos.configverify import verify_eapol
from vyos.ethtool import Ethtool
+from vyos.frrender import FRRender
from vyos.ifconfig import EthernetIf
from vyos.ifconfig import BondIf
-from vyos.template import render_to_string
from vyos.utils.dict import dict_search
from vyos.utils.dict import dict_to_paths_values
from vyos.utils.dict import dict_set
from vyos.utils.dict import dict_delete
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
+frrender = FRRender()
def update_bond_options(conf: Config, eth_conf: dict) -> list:
"""
@@ -164,6 +165,9 @@ def get_config(config=None):
tmp = is_node_changed(conf, base + [ifname, 'duplex'])
if tmp: ethernet.update({'speed_duplex_changed': {}})
+ tmp = is_node_changed(conf, base + [ifname, 'evpn'])
+ if tmp: ethernet.update({'frrender' : get_frrender_dict(conf)})
+
return ethernet
def verify_speed_duplex(ethernet: dict, ethtool: Ethtool):
@@ -318,32 +322,20 @@ def verify_ethernet(ethernet):
return None
def generate(ethernet):
- if 'deleted' in ethernet:
- return None
-
- ethernet['frr_zebra_config'] = ''
- if 'deleted' not in ethernet:
- ethernet['frr_zebra_config'] = render_to_string('frr/evpn.mh.frr.j2', ethernet)
-
+ if 'frrender' in ethernet:
+ frrender.generate(ethernet['frrender'])
return None
def apply(ethernet):
- ifname = ethernet['ifname']
+ if 'frrender' in ethernet:
+ frrender.apply()
- e = EthernetIf(ifname)
+ e = EthernetIf(ethernet['ifname'])
if 'deleted' in ethernet:
- # delete interface
e.remove()
else:
e.update(ethernet)
-
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
- frr_cfg.load_configuration(frr.mgmt_daemon)
- frr_cfg.modify_section(f'^interface {ifname}', stop_pattern='^exit', remove_stop_mark=True)
- if 'frr_zebra_config' in ethernet:
- frr_cfg.add_before(frr.default_add_before, ethernet['frr_zebra_config'])
- frr_cfg.commit_configuration()
+ return None
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py
index aef9b96c4..e6b6d474a 100755
--- a/src/conf_mode/policy.py
+++ b/src/conf_mode/policy.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2022 VyOS maintainers and contributors
+# Copyright (C) 2021-2024 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
@@ -17,15 +17,16 @@
from sys import exit
from vyos.config import Config
-from vyos.configdict import dict_merge
-from vyos.template import render_to_string
+from vyos.configdict import get_frrender_dict
+from vyos.configverify import has_frr_protocol_in_dict
+from vyos.frrender import FRRender
+from vyos.frrender import frr_protocols
from vyos.utils.dict import dict_search
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
-
airbag.enable()
+frrender = FRRender()
def community_action_compatibility(actions: dict) -> bool:
"""
@@ -87,31 +88,27 @@ def get_config(config=None):
else:
conf = Config()
- base = ['policy']
- policy = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
-
- # We also need some additional information from the config, prefix-lists
- # and route-maps for instance. They will be used in verify().
- #
- # XXX: one MUST always call this without the key_mangling() option! See
- # vyos.configverify.verify_common_route_maps() for more information.
- tmp = conf.get_config_dict(['protocols'], key_mangling=('-', '_'),
- no_tag_node_value_mangle=True)
- # Merge policy dict into "regular" config dict
- policy = dict_merge(tmp, policy)
- return policy
-
-
-def verify(policy):
- if not policy:
+ return get_frrender_dict(conf)
+
+
+def verify(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'policy'):
return None
- for policy_type in ['access_list', 'access_list6', 'as_path_list',
- 'community_list', 'extcommunity_list',
- 'large_community_list',
- 'prefix_list', 'prefix_list6', 'route_map']:
+ policy_types = ['access_list', 'access_list6', 'as_path_list',
+ 'community_list', 'extcommunity_list',
+ 'large_community_list', 'prefix_list',
+ 'prefix_list6', 'route_map']
+
+ policy = config_dict['policy']
+ for protocol in frr_protocols:
+ if protocol not in config_dict:
+ continue
+ if 'protocol' not in policy:
+ policy.update({'protocol': {}})
+ policy['protocol'].update({protocol : config_dict[protocol]})
+
+ for policy_type in policy_types:
# Bail out early and continue with next policy type
if policy_type not in policy:
continue
@@ -246,69 +243,33 @@ def verify(policy):
# When the "routing policy" changes and policies, route-maps etc. are deleted,
# it is our responsibility to verify that the policy can not be deleted if it
# is used by any routing protocol
- if 'protocols' in policy:
- for policy_type in ['access_list', 'access_list6', 'as_path_list',
- 'community_list',
- 'extcommunity_list', 'large_community_list',
- 'prefix_list', 'route_map']:
- if policy_type in policy:
- for policy_name in list(set(routing_policy_find(policy_type,
- policy[
- 'protocols']))):
- found = False
- if policy_name in policy[policy_type]:
- found = True
- # BGP uses prefix-list for selecting both an IPv4 or IPv6 AFI related
- # list - we need to go the extra mile here and check both prefix-lists
- if policy_type == 'prefix_list' and 'prefix_list6' in policy and policy_name in \
- policy['prefix_list6']:
- found = True
- if not found:
- tmp = policy_type.replace('_', '-')
- raise ConfigError(
- f'Can not delete {tmp} "{policy_name}", still in use!')
-
- return None
-
+ # Check if any routing protocol is activated
+ if 'protocol' in policy:
+ for policy_type in policy_types:
+ for policy_name in list(set(routing_policy_find(policy_type, policy['protocol']))):
+ found = False
+ if policy_type in policy and policy_name in policy[policy_type]:
+ found = True
+ # BGP uses prefix-list for selecting both an IPv4 or IPv6 AFI related
+ # list - we need to go the extra mile here and check both prefix-lists
+ if policy_type == 'prefix_list' and 'prefix_list6' in policy and policy_name in \
+ policy['prefix_list6']:
+ found = True
+ if not found:
+ tmp = policy_type.replace('_', '-')
+ raise ConfigError(
+ f'Can not delete {tmp} "{policy_name}", still in use!')
-def generate(policy):
- if not policy:
- return None
- policy['new_frr_config'] = render_to_string('frr/policy.frr.j2', policy)
return None
-def apply(policy):
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
-
- # The route-map used for the FIB (zebra) is part of the zebra daemon
- frr_cfg.load_configuration(frr.bgp_daemon)
- frr_cfg.modify_section(r'^bgp as-path access-list .*')
- frr_cfg.modify_section(r'^bgp community-list .*')
- frr_cfg.modify_section(r'^bgp extcommunity-list .*')
- frr_cfg.modify_section(r'^bgp large-community-list .*')
- frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit',
- remove_stop_mark=True)
- if 'new_frr_config' in policy:
- frr_cfg.add_before(frr.default_add_before, policy['new_frr_config'])
- frr_cfg.commit_configuration(frr.bgp_daemon)
-
- # The route-map used for the FIB (zebra) is part of the zebra daemon
- frr_cfg.load_configuration(frr.zebra_daemon)
- frr_cfg.modify_section(r'^access-list .*')
- frr_cfg.modify_section(r'^ipv6 access-list .*')
- frr_cfg.modify_section(r'^ip prefix-list .*')
- frr_cfg.modify_section(r'^ipv6 prefix-list .*')
- frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit',
- remove_stop_mark=True)
- if 'new_frr_config' in policy:
- frr_cfg.add_before(frr.default_add_before, policy['new_frr_config'])
- frr_cfg.commit_configuration(frr.zebra_daemon)
+def generate(config_dict):
+ frrender.generate(config_dict)
+def apply(config_dict):
+ frrender.apply()
return None
-
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/protocols_babel.py b/src/conf_mode/protocols_babel.py
index 06fd9b9b6..f9d5e45a0 100755
--- a/src/conf_mode/protocols_babel.py
+++ b/src/conf_mode/protocols_babel.py
@@ -17,63 +17,33 @@
from sys import exit
from vyos.config import Config
-from vyos.config import config_dict_merge
-from vyos.configdict import dict_merge
-from vyos.configdict import node_changed
+from vyos.configdict import get_frrender_dict
+from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_access_list
from vyos.configverify import verify_prefix_list
+from vyos.frrender import FRRender
from vyos.utils.dict import dict_search
-from vyos.template import render_to_string
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
+frrender = FRRender()
+
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- base = ['protocols', 'babel']
- babel = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True)
-
- # FRR has VRF support for different routing daemons. As interfaces belong
- # to VRFs - or the global VRF, we need to check for changed interfaces so
- # that they will be properly rendered for the FRR config. Also this eases
- # removal of interfaces from the running configuration.
- interfaces_removed = node_changed(conf, base + ['interface'])
- if interfaces_removed:
- babel['interface_removed'] = list(interfaces_removed)
-
- # Bail out early if configuration tree does not exist
- if not conf.exists(base):
- babel.update({'deleted' : ''})
- return babel
-
- # We have gathered the dict representation of the CLI, but there are default
- # values which we need to update into the dictionary retrieved.
- default_values = conf.get_config_defaults(base, key_mangling=('-', '_'),
- get_first_key=True,
- recursive=True)
- # merge in default values
- babel = config_dict_merge(default_values, babel)
+ return get_frrender_dict(conf)
- # We also need some additional information from the config, prefix-lists
- # and route-maps for instance. They will be used in verify().
- #
- # XXX: one MUST always call this without the key_mangling() option! See
- # vyos.configverify.verify_common_route_maps() for more information.
- tmp = conf.get_config_dict(['policy'])
- # Merge policy dict into "regular" config dict
- babel = dict_merge(tmp, babel)
- return babel
-
-def verify(babel):
- if not babel:
+def verify(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'babel'):
return None
+ babel = config_dict['babel']
+ babel['policy'] = config_dict['policy']
+
# verify distribute_list
if "distribute_list" in babel:
acl_keys = {
@@ -120,30 +90,11 @@ def verify(babel):
verify_prefix_list(prefix_list, babel, version='6' if address_family == 'ipv6' else '')
-def generate(babel):
- if not babel or 'deleted' in babel:
- return None
-
- babel['new_frr_config'] = render_to_string('frr/babeld.frr.j2', babel)
- return None
-
-def apply(babel):
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
-
- frr_cfg.load_configuration(frr.babel_daemon)
- frr_cfg.modify_section('^router babel', stop_pattern='^exit', remove_stop_mark=True)
-
- for key in ['interface', 'interface_removed']:
- if key not in babel:
- continue
- for interface in babel[key]:
- frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True)
-
- if 'new_frr_config' in babel:
- frr_cfg.add_before(frr.default_add_before, babel['new_frr_config'])
- frr_cfg.commit_configuration(frr.babel_daemon)
+def generate(config_dict):
+ frrender.generate(config_dict)
+def apply(config_dict):
+ frrender.apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py
index d94ec6a0d..a0d7fdfb5 100755
--- a/src/conf_mode/protocols_bfd.py
+++ b/src/conf_mode/protocols_bfd.py
@@ -15,36 +15,31 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from vyos.config import Config
+from vyos.configdict import get_frrender_dict
from vyos.configverify import verify_vrf
+from vyos.configverify import has_frr_protocol_in_dict
+from vyos.frrender import FRRender
from vyos.template import is_ipv6
-from vyos.template import render_to_string
from vyos.utils.network import is_ipv6_link_local
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
+frrender = FRRender()
+
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- base = ['protocols', 'bfd']
- bfd = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
- # Bail out early if configuration tree does not exist
- if not conf.exists(base):
- return bfd
-
- bfd = conf.merge_defaults(bfd, recursive=True)
- return bfd
+ return get_frrender_dict(conf)
-def verify(bfd):
- if not bfd:
+def verify(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'bfd'):
return None
+ bfd = config_dict['bfd']
if 'peer' in bfd:
for peer, peer_config in bfd['peer'].items():
# IPv6 link local peers require an explicit local address/interface
@@ -83,20 +78,11 @@ def verify(bfd):
return None
-def generate(bfd):
- if not bfd:
- return None
- bfd['new_frr_config'] = render_to_string('frr/bfdd.frr.j2', bfd)
-
-def apply(bfd):
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
- frr_cfg.load_configuration(frr.bfd_daemon)
- frr_cfg.modify_section('^bfd', stop_pattern='^exit', remove_stop_mark=True)
- if 'new_frr_config' in bfd:
- frr_cfg.add_before(frr.default_add_before, bfd['new_frr_config'])
- frr_cfg.commit_configuration(frr.bfd_daemon)
+def generate(config_dict):
+ frrender.generate(config_dict)
+def apply(config_dict):
+ frrender.apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index e5c46aee6..989cf9b5c 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -19,92 +19,35 @@ from sys import argv
from vyos.base import Warning
from vyos.config import Config
-from vyos.configdict import dict_merge
-from vyos.configdict import node_changed
+from vyos.configdict import get_frrender_dict
+from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_prefix_list
from vyos.configverify import verify_route_map
from vyos.configverify import verify_vrf
+from vyos.frrender import FRRender
from vyos.template import is_ip
from vyos.template import is_interface
-from vyos.template import render_to_string
from vyos.utils.dict import dict_search
from vyos.utils.network import get_interface_vrf
from vyos.utils.network import is_addr_assigned
from vyos.utils.process import process_named_running
-from vyos.utils.process import call
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
+frrender = FRRender()
+
+vrf = None
+if len(argv) > 1:
+ vrf = argv[1]
+
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- vrf = None
- if len(argv) > 1:
- vrf = argv[1]
-
- base_path = ['protocols', 'bgp']
-
- # eqivalent of the C foo ? 'a' : 'b' statement
- base = vrf and ['vrf', 'name', vrf, 'protocols', 'bgp'] or base_path
- bgp = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True, no_tag_node_value_mangle=True)
-
- bgp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'],
- key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
-
- # Remove per interface MPLS configuration - get a list if changed
- # nodes under the interface tagNode
- interfaces_removed = node_changed(conf, base + ['interface'])
- if interfaces_removed:
- bgp['interface_removed'] = list(interfaces_removed)
-
- # Assign the name of our VRF context. This MUST be done before the return
- # statement below, else on deletion we will delete the default instance
- # instead of the VRF instance.
- if vrf:
- bgp.update({'vrf' : vrf})
- # We can not delete the BGP VRF instance if there is a L3VNI configured
- # FRR L3VNI must be deleted first otherwise we will see error:
- # "FRR error: Please unconfigure l3vni 3000"
- tmp = ['vrf', 'name', vrf, 'vni']
- if conf.exists_effective(tmp):
- bgp.update({'vni' : conf.return_effective_value(tmp)})
- # We can safely delete ourself from the dependent vrf list
- if vrf in bgp['dependent_vrfs']:
- del bgp['dependent_vrfs'][vrf]
-
- bgp['dependent_vrfs'].update({'default': {'protocols': {
- 'bgp': conf.get_config_dict(base_path, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)}}})
-
- if not conf.exists(base):
- # If bgp instance is deleted then mark it
- bgp.update({'deleted' : ''})
- return bgp
-
- # We have gathered the dict representation of the CLI, but there are default
- # options which we need to update into the dictionary retrived.
- bgp = conf.merge_defaults(bgp, recursive=True)
-
- # We also need some additional information from the config, prefix-lists
- # and route-maps for instance. They will be used in verify().
- #
- # XXX: one MUST always call this without the key_mangling() option! See
- # vyos.configverify.verify_common_route_maps() for more information.
- tmp = conf.get_config_dict(['policy'])
- # Merge policy dict into "regular" config dict
- bgp = dict_merge(tmp, bgp)
-
- return bgp
-
+ return get_frrender_dict(conf)
def verify_vrf_as_import(search_vrf_name: str, afi_name: str, vrfs_config: dict) -> bool:
"""
@@ -237,7 +180,18 @@ def verify_afi(peer_config, bgp_config):
if tmp: return True
return False
-def verify(bgp):
+def verify(config_dict):
+ global vrf
+ if not has_frr_protocol_in_dict(config_dict, 'bgp', vrf):
+ return None
+
+ # eqivalent of the C foo ? 'a' : 'b' statement
+ bgp = vrf and config_dict['vrf']['name'][vrf]['protocols']['bgp'] or config_dict['bgp']
+ bgp['policy'] = config_dict['policy']
+
+ if vrf:
+ bgp['vrf'] = vrf
+
if 'deleted' in bgp:
if 'vrf' in bgp:
# Cannot delete vrf if it exists in import vrf list in other vrfs
@@ -252,8 +206,9 @@ def verify(bgp):
for vrf, vrf_options in bgp['dependent_vrfs'].items():
if vrf != 'default':
if dict_search('protocols.bgp', vrf_options):
- raise ConfigError('Cannot delete default BGP instance, ' \
- 'dependent VRF instance(s) exist(s)!')
+ dependent_vrfs = ', '.join(bgp['dependent_vrfs'].keys())
+ raise ConfigError(f'Cannot delete default BGP instance, ' \
+ f'dependent VRF instance(s): {dependent_vrfs}')
if 'vni' in vrf_options:
raise ConfigError('Cannot delete default BGP instance, ' \
'dependent L3VNI exists!')
@@ -602,44 +557,11 @@ def verify(bgp):
return None
-def generate(bgp):
- if not bgp or 'deleted' in bgp:
- return None
-
- bgp['frr_bgpd_config'] = render_to_string('frr/bgpd.frr.j2', bgp)
- return None
-
-def apply(bgp):
- if 'deleted' in bgp:
- # We need to ensure that the L3VNI is deleted first.
- # This is not possible with old config backend
- # priority bug
- if {'vrf', 'vni'} <= set(bgp):
- call('vtysh -c "conf t" -c "vrf {vrf}" -c "no vni {vni}"'.format(**bgp))
-
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
-
- # Generate empty helper string which can be ammended to FRR commands, it
- # will be either empty (default VRF) or contain the "vrf <name" statement
- vrf = ''
- if 'vrf' in bgp:
- vrf = ' vrf ' + bgp['vrf']
-
- frr_cfg.load_configuration(frr.bgp_daemon)
-
- # Remove interface specific config
- for key in ['interface', 'interface_removed']:
- if key not in bgp:
- continue
- for interface in bgp[key]:
- frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True)
-
- frr_cfg.modify_section(f'^router bgp \d+{vrf}', stop_pattern='^exit', remove_stop_mark=True)
- if 'frr_bgpd_config' in bgp:
- frr_cfg.add_before(frr.default_add_before, bgp['frr_bgpd_config'])
- frr_cfg.commit_configuration(frr.bgp_daemon)
+def generate(config_dict):
+ frrender.generate(config_dict)
+def apply(config_dict):
+ frrender.apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_eigrp.py b/src/conf_mode/protocols_eigrp.py
index a948b47da..5d60e6bfd 100755
--- a/src/conf_mode/protocols_eigrp.py
+++ b/src/conf_mode/protocols_eigrp.py
@@ -18,14 +18,18 @@ from sys import exit
from sys import argv
from vyos.config import Config
-from vyos.configdict import dict_merge
+from vyos.configdict import get_frrender_dict
+from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_vrf
-from vyos.template import render_to_string
+from vyos.frrender import FRRender
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
+frrender = FRRender()
+vrf = None
+if len(argv) > 1:
+ vrf = argv[1]
def get_config(config=None):
if config:
@@ -33,48 +37,16 @@ def get_config(config=None):
else:
conf = Config()
- vrf = None
- if len(argv) > 1:
- vrf = argv[1]
+ return get_frrender_dict(conf)
- base_path = ['protocols', 'eigrp']
+def verify(config_dict):
+ global vrf
+ if not has_frr_protocol_in_dict(config_dict, 'eigrp', vrf):
+ return None
# eqivalent of the C foo ? 'a' : 'b' statement
- base = vrf and ['vrf', 'name', vrf, 'protocols', 'eigrp'] or base_path
- eigrp = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True, no_tag_node_value_mangle=True)
-
- # Assign the name of our VRF context. This MUST be done before the return
- # statement below, else on deletion we will delete the default instance
- # instead of the VRF instance.
- if vrf: eigrp.update({'vrf' : vrf})
-
- if not conf.exists(base):
- eigrp.update({'deleted' : ''})
- if not vrf:
- # We are running in the default VRF context, thus we can not delete
- # our main EIGRP instance if there are dependent EIGRP VRF instances.
- eigrp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'],
- key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
-
- return eigrp
-
- # We also need some additional information from the config, prefix-lists
- # and route-maps for instance. They will be used in verify().
- #
- # XXX: one MUST always call this without the key_mangling() option! See
- # vyos.configverify.verify_common_route_maps() for more information.
- tmp = conf.get_config_dict(['policy'])
- # Merge policy dict into "regular" config dict
- eigrp = dict_merge(tmp, eigrp)
-
- return eigrp
-
-def verify(eigrp):
- if not eigrp or 'deleted' in eigrp:
- return
+ eigrp = vrf and config_dict['vrf']['name'][vrf]['protocols']['eigrp'] or config_dict['eigrp']
+ eigrp['policy'] = config_dict['policy']
if 'system_as' not in eigrp:
raise ConfigError('EIGRP system-as must be defined!')
@@ -82,28 +54,11 @@ def verify(eigrp):
if 'vrf' in eigrp:
verify_vrf(eigrp)
-def generate(eigrp):
- if not eigrp or 'deleted' in eigrp:
- return None
-
- eigrp['frr_eigrpd_config'] = render_to_string('frr/eigrpd.frr.j2', eigrp)
-
-def apply(eigrp):
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
-
- # Generate empty helper string which can be ammended to FRR commands, it
- # will be either empty (default VRF) or contain the "vrf <name" statement
- vrf = ''
- if 'vrf' in eigrp:
- vrf = ' vrf ' + eigrp['vrf']
-
- frr_cfg.load_configuration(frr.eigrp_daemon)
- frr_cfg.modify_section(f'^router eigrp \d+{vrf}', stop_pattern='^exit', remove_stop_mark=True)
- if 'frr_eigrpd_config' in eigrp:
- frr_cfg.add_before(frr.default_add_before, eigrp['frr_eigrpd_config'])
- frr_cfg.commit_configuration(frr.eigrp_daemon)
+def generate(config_dict):
+ frrender.generate(config_dict)
+def apply(config_dict):
+ frrender.apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py
index 9b70c7329..e812770bc 100755
--- a/src/conf_mode/protocols_isis.py
+++ b/src/conf_mode/protocols_isis.py
@@ -18,75 +18,89 @@ from sys import exit
from sys import argv
from vyos.config import Config
-from vyos.configdict import dict_merge
-from vyos.configdict import node_changed
+from vyos.configdict import get_frrender_dict
+from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_common_route_maps
from vyos.configverify import verify_interface_exists
+from vyos.frrender import FRRender
from vyos.ifconfig import Interface
from vyos.utils.dict import dict_search
from vyos.utils.network import get_interface_config
-from vyos.template import render_to_string
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
+frrender = FRRender()
+
+vrf = None
+if len(argv) > 1:
+ vrf = argv[1]
+
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- vrf = None
- if len(argv) > 1:
- vrf = argv[1]
+ return get_frrender_dict(conf)
- base_path = ['protocols', 'isis']
- # eqivalent of the C foo ? 'a' : 'b' statement
- base = vrf and ['vrf', 'name', vrf, 'protocols', 'isis'] or base_path
- isis = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
-
- # Assign the name of our VRF context. This MUST be done before the return
- # statement below, else on deletion we will delete the default instance
- # instead of the VRF instance.
- if vrf: isis['vrf'] = vrf
-
- # 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:
- isis['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):
- isis.update({'deleted' : ''})
- return isis
-
- # merge in default values
- isis = conf.merge_defaults(isis, recursive=True)
-
- # We also need some additional information from the config, prefix-lists
- # and route-maps for instance. They will be used in verify().
- #
- # XXX: one MUST always call this without the key_mangling() option! See
- # vyos.configverify.verify_common_route_maps() for more information.
- tmp = conf.get_config_dict(['policy'])
- # Merge policy dict into "regular" config dict
- isis = dict_merge(tmp, isis)
+ # base_path = ['protocols', 'isis']
+
+ # # eqivalent of the C foo ? 'a' : 'b' statement
+ # base = vrf and ['vrf', 'name', vrf, 'protocols', 'isis'] or base_path
+ # isis = conf.get_config_dict(base, key_mangling=('-', '_'),
+ # get_first_key=True,
+ # no_tag_node_value_mangle=True)
+
+ # # Assign the name of our VRF context. This MUST be done before the return
+ # # statement below, else on deletion we will delete the default instance
+ # # instead of the VRF instance.
+ # if vrf: isis['vrf'] = vrf
+
+ # # 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:
+ # isis['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):
+ # isis.update({'deleted' : ''})
+ # return isis
+
+ # # merge in default values
+ # isis = conf.merge_defaults(isis, recursive=True)
+
+ # # We also need some additional information from the config, prefix-lists
+ # # and route-maps for instance. They will be used in verify().
+ # #
+ # # XXX: one MUST always call this without the key_mangling() option! See
+ # # vyos.configverify.verify_common_route_maps() for more information.
+ # tmp = conf.get_config_dict(['policy'])
+ # # Merge policy dict into "regular" config dict
+ # isis = dict_merge(tmp, isis)
return isis
-def verify(isis):
- # bail out early - looks like removal from running config
- if not isis or 'deleted' in isis:
+def verify(config_dict):
+ global vrf
+ if not has_frr_protocol_in_dict(config_dict, 'isis', vrf):
return None
+ # eqivalent of the C foo ? 'a' : 'b' statement
+ isis = vrf and config_dict['vrf']['name'][vrf]['protocols']['isis'] or config_dict['isis']
+ isis['policy'] = config_dict['policy']
+
+ if 'deleted' in isis:
+ return None
+
+ if vrf:
+ isis['vrf'] = vrf
+
if 'net' not in isis:
raise ConfigError('Network entity is mandatory!')
@@ -266,37 +280,11 @@ def verify(isis):
return None
-def generate(isis):
- if not isis or 'deleted' in isis:
- return None
-
- isis['frr_isisd_config'] = render_to_string('frr/isisd.frr.j2', isis)
- return None
-
-def apply(isis):
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
-
- # Generate empty helper string which can be ammended to FRR commands, it
- # will be either empty (default VRF) or contain the "vrf <name" statement
- vrf = ''
- if 'vrf' in isis:
- vrf = ' vrf ' + isis['vrf']
-
- frr_cfg.load_configuration(frr.isis_daemon)
- frr_cfg.modify_section(f'^router isis VyOS{vrf}', stop_pattern='^exit', remove_stop_mark=True)
-
- for key in ['interface', 'interface_removed']:
- if key not in isis:
- continue
- for interface in isis[key]:
- frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True)
-
- if 'frr_isisd_config' in isis:
- frr_cfg.add_before(frr.default_add_before, isis['frr_isisd_config'])
-
- frr_cfg.commit_configuration(frr.isis_daemon)
+def generate(config_dict):
+ frrender.generate(config_dict)
+def apply(config_dict):
+ frrender.apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py
index 4a05b8044..2a691f4a4 100755
--- a/src/conf_mode/protocols_mpls.py
+++ b/src/conf_mode/protocols_mpls.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2022 VyOS maintainers and contributors
+# Copyright (C) 2020-2024 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
@@ -20,33 +20,33 @@ from sys import exit
from glob import glob
from vyos.config import Config
-from vyos.template import render_to_string
+from vyos.configdict import get_frrender_dict
+from vyos.configverify import has_frr_protocol_in_dict
+from vyos.frrender import FRRender
from vyos.utils.dict import dict_search
from vyos.utils.file import read_file
from vyos.utils.system import sysctl_write
from vyos.configverify import verify_interface_exists
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
-config_file = r'/tmp/ldpd.frr'
+frrender = FRRender()
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- base = ['protocols', 'mpls']
- mpls = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- return mpls
+ return get_frrender_dict(conf)
-def verify(mpls):
- # If no config, then just bail out early.
- if not mpls:
+def verify(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'mpls'):
return None
+ mpls = config_dict['mpls']
+
if 'interface' in mpls:
for interface in mpls['interface']:
verify_interface_exists(mpls, interface)
@@ -68,24 +68,16 @@ def verify(mpls):
return None
-def generate(mpls):
- # If there's no MPLS config generated, create dictionary key with no value.
- if not mpls or 'deleted' in mpls:
- return None
+def generate(config_dict):
+ frrender.generate(config_dict)
- mpls['frr_ldpd_config'] = render_to_string('frr/ldpd.frr.j2', mpls)
- return None
+def apply(config_dict):
+ frrender.apply()
-def apply(mpls):
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
-
- frr_cfg.load_configuration(frr.ldpd_daemon)
- frr_cfg.modify_section(f'^mpls ldp', stop_pattern='^exit', remove_stop_mark=True)
+ if not has_frr_protocol_in_dict(config_dict, 'mpls'):
+ return None
- if 'frr_ldpd_config' in mpls:
- frr_cfg.add_before(frr.default_add_before, mpls['frr_ldpd_config'])
- frr_cfg.commit_configuration(frr.ldpd_daemon)
+ mpls = config_dict['mpls']
# Set number of entries in the platform label tables
labels = '0'
diff --git a/src/conf_mode/protocols_openfabric.py b/src/conf_mode/protocols_openfabric.py
index b60117a02..3dc06ee68 100644
--- a/src/conf_mode/protocols_openfabric.py
+++ b/src/conf_mode/protocols_openfabric.py
@@ -18,14 +18,15 @@ from sys import exit
from vyos.base import Warning
from vyos.config import Config
-from vyos.configdict import node_changed
+from vyos.configdict import get_frrender_dict
from vyos.configverify import verify_interface_exists
-from vyos.template import render_to_string
+from vyos.configverify import has_frr_protocol_in_dict
+from vyos.frrender import FRRender
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
+frrender = FRRender()
def get_config(config=None):
if config:
@@ -33,32 +34,14 @@ def get_config(config=None):
else:
conf = Config()
- base_path = ['protocols', 'openfabric']
+ return get_frrender_dict(conf)
- openfabric = conf.get_config_dict(base_path, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
-
- # Remove per domain MPLS configuration - get a list of all changed Openfabric domains
- # (removed and added) so that they will be properly rendered for the FRR config.
- openfabric['domains_all'] = list(conf.list_nodes(' '.join(base_path) + f' domain') +
- node_changed(conf, base_path + ['domain']))
-
- # Get a list of all interfaces
- openfabric['interfaces_all'] = []
- for domain in openfabric['domains_all']:
- interfaces_modified = list(node_changed(conf, base_path + ['domain', domain, 'interface']) +
- conf.list_nodes(' '.join(base_path) + f' domain {domain} interface'))
- openfabric['interfaces_all'].extend(interfaces_modified)
-
- if not conf.exists(base_path):
- openfabric.update({'deleted': ''})
-
- return openfabric
+def verify(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'openfabric'):
+ return None
-def verify(openfabric):
- # bail out early - looks like removal from running config
- if not openfabric or 'deleted' in openfabric:
+ openfabric = config_dict['openfabric']
+ if 'deleted' in openfabric:
return None
if 'net' not in openfabric:
@@ -107,29 +90,11 @@ def verify(openfabric):
return None
-def generate(openfabric):
- if not openfabric or 'deleted' in openfabric:
- return None
-
- openfabric['frr_fabricd_config'] = render_to_string('frr/fabricd.frr.j2', openfabric)
- return None
-
-def apply(openfabric):
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
-
- frr_cfg.load_configuration(frr.openfabric_daemon)
- for domain in openfabric['domains_all']:
- frr_cfg.modify_section(f'^router openfabric {domain}', stop_pattern='^exit', remove_stop_mark=True)
-
- for interface in openfabric['interfaces_all']:
- frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True)
-
- if 'frr_fabricd_config' in openfabric:
- frr_cfg.add_before(frr.default_add_before, openfabric['frr_fabricd_config'])
-
- frr_cfg.commit_configuration(frr.openfabric_daemon)
+def generate(config_dict):
+ frrender.generate(config_dict)
+def apply(config_dict):
+ frrender.apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index 44817b9b7..32ad5e497 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -18,20 +18,23 @@ from sys import exit
from sys import argv
from vyos.config import Config
-from vyos.config import config_dict_merge
-from vyos.configdict import dict_merge
-from vyos.configdict import node_changed
+from vyos.configdict import get_frrender_dict
from vyos.configverify import verify_common_route_maps
from vyos.configverify import verify_route_map
from vyos.configverify import verify_interface_exists
from vyos.configverify import verify_access_list
-from vyos.template import render_to_string
+from vyos.configverify import has_frr_protocol_in_dict
+from vyos.frrender import FRRender
from vyos.utils.dict import dict_search
from vyos.utils.network import get_interface_config
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
+frrender = FRRender()
+
+vrf = None
+if len(argv) > 1:
+ vrf = argv[1]
def get_config(config=None):
if config:
@@ -39,85 +42,16 @@ def get_config(config=None):
else:
conf = Config()
- vrf = None
- if len(argv) > 1:
- vrf = argv[1]
+ return get_frrender_dict(conf)
- base_path = ['protocols', 'ospf']
+def verify(config_dict):
+ global vrf
+ if not has_frr_protocol_in_dict(config_dict, 'ospf', vrf):
+ return None
# eqivalent of the C foo ? 'a' : 'b' statement
- base = vrf and ['vrf', 'name', vrf, 'protocols', 'ospf'] or base_path
- ospf = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True)
-
- # Assign the name of our VRF context. This MUST be done before the return
- # statement below, else on deletion we will delete the default instance
- # instead of the VRF instance.
- if vrf: ospf['vrf'] = vrf
-
- # 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:
- ospf['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):
- ospf.update({'deleted' : ''})
- return ospf
-
- # 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(**ospf.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.
- if dict_search('default_information.originate', ospf) is None:
- del default_values['default_information']
- if 'mpls_te' not in ospf:
- del default_values['mpls_te']
- if 'graceful_restart' not in ospf:
- del default_values['graceful_restart']
- for area_num in default_values.get('area', []):
- if dict_search(f'area.{area_num}.area_type.nssa', ospf) is None:
- del default_values['area'][area_num]['area_type']['nssa']
-
- for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']:
- if dict_search(f'redistribute.{protocol}', ospf) is None:
- del default_values['redistribute'][protocol]
- if not bool(default_values['redistribute']):
- del default_values['redistribute']
-
- for interface in ospf.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 'hello_multiplier' in ospf['interface'][interface]:
- del default_values['interface'][interface]['dead_interval']
-
- ospf = config_dict_merge(default_values, ospf)
-
- # We also need some additional information from the config, prefix-lists
- # and route-maps for instance. They will be used in verify().
- #
- # XXX: one MUST always call this without the key_mangling() option! See
- # vyos.configverify.verify_common_route_maps() for more information.
- tmp = conf.get_config_dict(['policy'])
- # Merge policy dict into "regular" config dict
- ospf = dict_merge(tmp, ospf)
-
- return ospf
-
-def verify(ospf):
- if not ospf:
- return None
+ ospf = vrf and config_dict['vrf']['name'][vrf]['protocols']['ospf'] or config_dict['ospf']
+ ospf['policy'] = config_dict['policy']
verify_common_route_maps(ospf)
@@ -164,8 +98,7 @@ def verify(ospf):
# interface is bound to our requesting VRF. Due to the VyOS
# priorities the interface is bound to the VRF after creation of
# the VRF itself, and before any routing protocol is configured.
- if 'vrf' in ospf:
- vrf = ospf['vrf']
+ if vrf:
tmp = get_interface_config(interface)
if 'master' not in tmp or tmp['master'] != vrf:
raise ConfigError(f'Interface "{interface}" is not a member of VRF "{vrf}"!')
@@ -244,37 +177,11 @@ def verify(ospf):
return None
-def generate(ospf):
- if not ospf or 'deleted' in ospf:
- return None
-
- ospf['frr_ospfd_config'] = render_to_string('frr/ospfd.frr.j2', ospf)
- return None
-
-def apply(ospf):
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
-
- # Generate empty helper string which can be ammended to FRR commands, it
- # will be either empty (default VRF) or contain the "vrf <name" statement
- vrf = ''
- if 'vrf' in ospf:
- vrf = ' vrf ' + ospf['vrf']
-
- frr_cfg.load_configuration(frr.ospf_daemon)
- frr_cfg.modify_section(f'^router ospf{vrf}', stop_pattern='^exit', remove_stop_mark=True)
-
- for key in ['interface', 'interface_removed']:
- if key not in ospf:
- continue
- for interface in ospf[key]:
- frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True)
-
- if 'frr_ospfd_config' in ospf:
- frr_cfg.add_before(frr.default_add_before, ospf['frr_ospfd_config'])
-
- frr_cfg.commit_configuration(frr.ospf_daemon)
+def generate(config_dict):
+ frrender.generate(config_dict)
+def apply(config_dict):
+ frrender.apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py
index 7bdab3017..038fcd2b4 100755
--- a/src/conf_mode/protocols_ospfv3.py
+++ b/src/conf_mode/protocols_ospfv3.py
@@ -18,96 +18,41 @@ from sys import exit
from sys import argv
from vyos.config import Config
-from vyos.config import config_dict_merge
-from vyos.configdict import dict_merge
-from vyos.configdict import node_changed
+from vyos.configdict import get_frrender_dict
from vyos.configverify import verify_common_route_maps
from vyos.configverify import verify_route_map
from vyos.configverify import verify_interface_exists
-from vyos.template import render_to_string
+from vyos.configverify import has_frr_protocol_in_dict
+from vyos.frrender import FRRender
from vyos.ifconfig import Interface
from vyos.utils.dict import dict_search
from vyos.utils.network import get_interface_config
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
+frrender = FRRender()
+
+vrf = None
+if len(argv) > 1:
+ vrf = argv[1]
+
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- vrf = None
- if len(argv) > 1:
- vrf = argv[1]
+ return get_frrender_dict(conf)
- base_path = ['protocols', 'ospfv3']
+def verify(config_dict):
+ global vrf
+ if not has_frr_protocol_in_dict(config_dict, 'ospfv3', vrf):
+ return None
# eqivalent of the C foo ? 'a' : 'b' statement
- base = vrf and ['vrf', 'name', vrf, 'protocols', 'ospfv3'] or base_path
- ospfv3 = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
-
- # Assign the name of our VRF context. This MUST be done before the return
- # statement below, else on deletion we will delete the default instance
- # instead of the VRF instance.
- if vrf: ospfv3['vrf'] = vrf
-
- # 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:
- ospfv3['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):
- ospfv3.update({'deleted' : ''})
- return ospfv3
-
- # 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(**ospfv3.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.
- if dict_search('default_information.originate', ospfv3) is None:
- del default_values['default_information']
- if 'graceful_restart' not in ospfv3:
- del default_values['graceful_restart']
-
- for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'ripng', 'static']:
- if dict_search(f'redistribute.{protocol}', ospfv3) is None:
- del default_values['redistribute'][protocol]
- if not bool(default_values['redistribute']):
- del default_values['redistribute']
-
- default_values.pop('interface', {})
-
- # merge in remaining default values
- ospfv3 = config_dict_merge(default_values, ospfv3)
-
- # We also need some additional information from the config, prefix-lists
- # and route-maps for instance. They will be used in verify().
- #
- # XXX: one MUST always call this without the key_mangling() option! See
- # vyos.configverify.verify_common_route_maps() for more information.
- tmp = conf.get_config_dict(['policy'])
- # Merge policy dict into "regular" config dict
- ospfv3 = dict_merge(tmp, ospfv3)
-
- return ospfv3
-
-def verify(ospfv3):
- if not ospfv3:
- return None
+ ospfv3 = vrf and config_dict['vrf']['name'][vrf]['protocols']['ospfv3'] or config_dict['ospfv3']
+ ospfv3['policy'] = config_dict['policy']
verify_common_route_maps(ospfv3)
@@ -137,45 +82,18 @@ def verify(ospfv3):
# interface is bound to our requesting VRF. Due to the VyOS
# priorities the interface is bound to the VRF after creation of
# the VRF itself, and before any routing protocol is configured.
- if 'vrf' in ospfv3:
- vrf = ospfv3['vrf']
+ if vrf:
tmp = get_interface_config(interface)
if 'master' not in tmp or tmp['master'] != vrf:
raise ConfigError(f'Interface "{interface}" is not a member of VRF "{vrf}"!')
return None
-def generate(ospfv3):
- if not ospfv3 or 'deleted' in ospfv3:
- return None
-
- ospfv3['new_frr_config'] = render_to_string('frr/ospf6d.frr.j2', ospfv3)
- return None
-
-def apply(ospfv3):
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
-
- # Generate empty helper string which can be ammended to FRR commands, it
- # will be either empty (default VRF) or contain the "vrf <name" statement
- vrf = ''
- if 'vrf' in ospfv3:
- vrf = ' vrf ' + ospfv3['vrf']
-
- frr_cfg.load_configuration(frr.ospf6_daemon)
- frr_cfg.modify_section(f'^router ospf6{vrf}', stop_pattern='^exit', remove_stop_mark=True)
-
- for key in ['interface', 'interface_removed']:
- if key not in ospfv3:
- continue
- for interface in ospfv3[key]:
- frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True)
-
- if 'new_frr_config' in ospfv3:
- frr_cfg.add_before(frr.default_add_before, ospfv3['new_frr_config'])
-
- frr_cfg.commit_configuration(frr.ospf6_daemon)
+def generate(config_dict):
+ frrender.generate(config_dict)
+def apply(config_dict):
+ frrender.apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py
index 994007137..3862530a2 100755
--- a/src/conf_mode/protocols_rip.py
+++ b/src/conf_mode/protocols_rip.py
@@ -17,58 +17,34 @@
from sys import exit
from vyos.config import Config
-from vyos.configdict import dict_merge
-from vyos.configdict import node_changed
+from vyos.configdict import get_frrender_dict
+from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_common_route_maps
from vyos.configverify import verify_access_list
from vyos.configverify import verify_prefix_list
+from vyos.frrender import FRRender
from vyos.utils.dict import dict_search
-from vyos.template import render_to_string
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
+frrender = FRRender()
+
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- base = ['protocols', 'rip']
- rip = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
-
- # FRR has VRF support for different routing daemons. As interfaces belong
- # to VRFs - or the global VRF, we need to check for changed interfaces so
- # that they will be properly rendered for the FRR config. Also this eases
- # removal of interfaces from the running configuration.
- interfaces_removed = node_changed(conf, base + ['interface'])
- if interfaces_removed:
- rip['interface_removed'] = list(interfaces_removed)
-
- # Bail out early if configuration tree does not exist
- if not conf.exists(base):
- rip.update({'deleted' : ''})
- return rip
-
- # We have gathered the dict representation of the CLI, but there are default
- # options which we need to update into the dictionary retrived.
- rip = conf.merge_defaults(rip, recursive=True)
-
- # We also need some additional information from the config, prefix-lists
- # and route-maps for instance. They will be used in verify().
- #
- # XXX: one MUST always call this without the key_mangling() option! See
- # vyos.configverify.verify_common_route_maps() for more information.
- tmp = conf.get_config_dict(['policy'])
- # Merge policy dict into "regular" config dict
- rip = dict_merge(tmp, rip)
-
- return rip
-
-def verify(rip):
- if not rip:
+
+ return get_frrender_dict(conf)
+
+def verify(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'rip'):
return None
+ rip = config_dict['rip']
+ rip['policy'] = config_dict['policy']
+
verify_common_route_maps(rip)
acl_in = dict_search('distribute_list.access_list.in', rip)
@@ -93,33 +69,11 @@ def verify(rip):
raise ConfigError(f'You can not have "split-horizon poison-reverse" enabled ' \
f'with "split-horizon disable" for "{interface}"!')
-def generate(rip):
- if not rip or 'deleted' in rip:
- return None
-
- rip['new_frr_config'] = render_to_string('frr/ripd.frr.j2', rip)
- return None
+def generate(frr_dict):
+ frrender.generate(frr_dict)
def apply(rip):
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
-
- # The route-map used for the FIB (zebra) is part of the mgmt daemon as of FRR 10.1
- frr_cfg.load_configuration(frr.mgmt_daemon)
- frr_cfg.modify_section('^ip protocol rip route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)')
- frr_cfg.modify_section('^key chain \S+', stop_pattern='^exit', remove_stop_mark=True)
- frr_cfg.modify_section('^router rip', stop_pattern='^exit', remove_stop_mark=True)
-
- for key in ['interface', 'interface_removed']:
- if key not in rip:
- continue
- for interface in rip[key]:
- frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True)
-
- if 'new_frr_config' in rip:
- frr_cfg.add_before(frr.default_add_before, rip['new_frr_config'])
- frr_cfg.commit_configuration()
-
+ frrender.apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_ripng.py b/src/conf_mode/protocols_ripng.py
index 9d4447d1f..327a0d239 100755
--- a/src/conf_mode/protocols_ripng.py
+++ b/src/conf_mode/protocols_ripng.py
@@ -17,48 +17,34 @@
from sys import exit
from vyos.config import Config
-from vyos.configdict import dict_merge
+from vyos.configdict import get_frrender_dict
+from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_common_route_maps
from vyos.configverify import verify_access_list
from vyos.configverify import verify_prefix_list
+from vyos.frrender import FRRender
from vyos.utils.dict import dict_search
-from vyos.template import render_to_string
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
+frrender = FRRender()
+
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- base = ['protocols', 'ripng']
- ripng = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
-
- # Bail out early if configuration tree does not exist
- if not conf.exists(base):
- return ripng
-
- # We have gathered the dict representation of the CLI, but there are default
- # options which we need to update into the dictionary retrived.
- ripng = conf.merge_defaults(ripng, recursive=True)
-
- # We also need some additional information from the config, prefix-lists
- # and route-maps for instance. They will be used in verify().
- #
- # XXX: one MUST always call this without the key_mangling() option! See
- # vyos.configverify.verify_common_route_maps() for more information.
- tmp = conf.get_config_dict(['policy'])
- # Merge policy dict into "regular" config dict
- ripng = dict_merge(tmp, ripng)
-
- return ripng
-
-def verify(ripng):
- if not ripng:
+
+ return get_frrender_dict(conf)
+
+def verify(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'ripng'):
return None
+ ripng = config_dict['ripng']
+ ripng['policy'] = config_dict['policy']
+
verify_common_route_maps(ripng)
acl_in = dict_search('distribute_list.access_list.in', ripng)
@@ -83,25 +69,12 @@ def verify(ripng):
raise ConfigError(f'You can not have "split-horizon poison-reverse" enabled ' \
f'with "split-horizon disable" for "{interface}"!')
-def generate(ripng):
- if not ripng:
- return None
- ripng['new_frr_config'] = render_to_string('frr/ripngd.frr.j2', ripng)
-
-def apply(ripng):
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
-
- # The route-map used for the FIB (zebra) is part of the zebra daemon
- frr_cfg.load_configuration(frr.mgmt_daemon)
- frr_cfg.modify_section('^ipv6 protocol ripng route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)')
- frr_cfg.modify_section('key chain \S+', stop_pattern='^exit', remove_stop_mark=True)
- frr_cfg.modify_section('interface \S+', stop_pattern='^exit', remove_stop_mark=True)
- frr_cfg.modify_section('^router ripng', stop_pattern='^exit', remove_stop_mark=True)
- if 'new_frr_config' in ripng:
- frr_cfg.add_before(frr.default_add_before, ripng['new_frr_config'])
- frr_cfg.commit_configuration()
+def generate(config_dict):
+ frrender.generate(config_dict)
+ return None
+def apply(config_dict):
+ frrender.apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py
index bec0cda91..c75f95860 100755
--- a/src/conf_mode/protocols_rpki.py
+++ b/src/conf_mode/protocols_rpki.py
@@ -20,15 +20,17 @@ from glob import glob
from sys import exit
from vyos.config import Config
+from vyos.configdict import get_frrender_dict
+from vyos.configverify import has_frr_protocol_in_dict
+from vyos.frrender import FRRender
from vyos.pki import wrap_openssh_public_key
from vyos.pki import wrap_openssh_private_key
-from vyos.template import render_to_string
from vyos.utils.dict import dict_search_args
from vyos.utils.file import write_file
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
+frrender = FRRender()
rpki_ssh_key_base = '/run/frr/id_rpki'
@@ -37,25 +39,14 @@ def get_config(config=None):
conf = config
else:
conf = Config()
- base = ['protocols', 'rpki']
+ return get_frrender_dict(conf)
- rpki = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True, with_pki=True)
- # Bail out early if configuration tree does not exist
- if not conf.exists(base):
- rpki.update({'deleted' : ''})
- return rpki
-
- # We have gathered the dict representation of the CLI, but there are default
- # options which we need to update into the dictionary retrived.
- rpki = conf.merge_defaults(rpki, recursive=True)
-
- return rpki
-
-def verify(rpki):
- if not rpki:
+def verify(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'rpki'):
return None
+ rpki = config_dict['rpki']
+
if 'cache' in rpki:
preferences = []
for peer, peer_config in rpki['cache'].items():
@@ -81,12 +72,14 @@ def verify(rpki):
return None
-def generate(rpki):
+def generate(config_dict):
for key in glob(f'{rpki_ssh_key_base}*'):
os.unlink(key)
- if not rpki:
- return
+ if not has_frr_protocol_in_dict(config_dict, 'rpki'):
+ return None
+
+ rpki = config_dict['rpki']
if 'cache' in rpki:
for cache, cache_config in rpki['cache'].items():
@@ -102,19 +95,11 @@ def generate(rpki):
write_file(cache_config['ssh']['public_key_file'], wrap_openssh_public_key(public_key_data, public_key_type))
write_file(cache_config['ssh']['private_key_file'], wrap_openssh_private_key(private_key_data))
- rpki['new_frr_config'] = render_to_string('frr/rpki.frr.j2', rpki)
-
+ frrender.generate(config_dict)
return None
-def apply(rpki):
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
- frr_cfg.load_configuration(frr.bgp_daemon)
- frr_cfg.modify_section('^rpki', stop_pattern='^exit', remove_stop_mark=True)
- if 'new_frr_config' in rpki:
- frr_cfg.add_before(frr.default_add_before, rpki['new_frr_config'])
-
- frr_cfg.commit_configuration(frr.bgp_daemon)
+def apply(config_dict):
+ frrender.apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_segment-routing.py b/src/conf_mode/protocols_segment-routing.py
index 67f8005ef..0fad968e1 100755
--- a/src/conf_mode/protocols_segment-routing.py
+++ b/src/conf_mode/protocols_segment-routing.py
@@ -17,14 +17,17 @@
from sys import exit
from vyos.config import Config
-from vyos.configdict import node_changed
-from vyos.template import render_to_string
+from vyos.configdict import get_frrender_dict
+from vyos.configdict import list_diff
+from vyos.configverify import has_frr_protocol_in_dict
+from vyos.frrender import FRRender
+from vyos.ifconfig import Section
from vyos.utils.dict import dict_search
from vyos.utils.system import sysctl_write
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
+frrender = FRRender()
def get_config(config=None):
if config:
@@ -32,25 +35,14 @@ def get_config(config=None):
else:
conf = Config()
- base = ['protocols', 'segment-routing']
- sr = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True,
- with_recursive_defaults=True)
+ return get_frrender_dict(conf)
- # 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:
- sr['interface_removed'] = list(interfaces_removed)
+def verify(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'segment_routing'):
+ return None
- import pprint
- pprint.pprint(sr)
- return sr
+ sr = config_dict['segment_routing']
-def verify(sr):
if 'srv6' in sr:
srv6_enable = False
if 'interface' in sr:
@@ -62,45 +54,41 @@ def verify(sr):
raise ConfigError('SRv6 should be enabled on at least one interface!')
return None
-def generate(sr):
- if not sr:
+def generate(config_dict):
+ frrender.generate(config_dict)
+ return None
+
+def apply(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'segment_routing'):
return None
- sr['new_frr_config'] = render_to_string('frr/zebra.segment_routing.frr.j2', sr)
- return None
+ sr = config_dict['segment_routing']
-def apply(sr):
- if 'interface_removed' in sr:
- for interface in sr['interface_removed']:
- # Disable processing of IPv6-SR packets
- sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '0')
+ current_interfaces = Section.interfaces()
+ sr_interfaces = list(sr.get('interface', {}).keys())
- if 'interface' in sr:
- for interface, interface_config in sr['interface'].items():
- # Accept or drop SR-enabled IPv6 packets on this interface
- if 'srv6' in interface_config:
- sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '1')
- # Define HMAC policy for ingress SR-enabled packets on this interface
- # It's a redundant check as HMAC has a default value - but better safe
- # then sorry
- tmp = dict_search('srv6.hmac', interface_config)
- if tmp == 'accept':
- sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '0')
- elif tmp == 'drop':
- sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '1')
- elif tmp == 'ignore':
- sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '-1')
- else:
- sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '0')
+ for interface in list_diff(current_interfaces, sr_interfaces):
+ # Disable processing of IPv6-SR packets
+ sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '0')
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
- frr_cfg.load_configuration(frr.zebra_daemon)
- frr_cfg.modify_section(r'^segment-routing')
- if 'new_frr_config' in sr:
- frr_cfg.add_before(frr.default_add_before, sr['new_frr_config'])
- frr_cfg.commit_configuration(frr.zebra_daemon)
+ for interface, interface_config in sr.get('interface', {}).items():
+ # Accept or drop SR-enabled IPv6 packets on this interface
+ if 'srv6' in interface_config:
+ sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '1')
+ # Define HMAC policy for ingress SR-enabled packets on this interface
+ # It's a redundant check as HMAC has a default value - but better safe
+ # then sorry
+ tmp = dict_search('srv6.hmac', interface_config)
+ if tmp == 'accept':
+ sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '0')
+ elif tmp == 'drop':
+ sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '1')
+ elif tmp == 'ignore':
+ sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '-1')
+ else:
+ sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '0')
+ frrender.apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py
index c5dc77fd2..1e498b256 100755
--- a/src/conf_mode/protocols_static.py
+++ b/src/conf_mode/protocols_static.py
@@ -14,21 +14,25 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+from ipaddress import IPv4Network
from sys import exit
from sys import argv
from vyos.config import Config
-from vyos.configdict import dict_merge
-from vyos.configdict import get_dhcp_interfaces
-from vyos.configdict import get_pppoe_interfaces
+from vyos.configdict import get_frrender_dict
+from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_common_route_maps
from vyos.configverify import verify_vrf
+from vyos.frrender import FRRender
from vyos.template import render
-from vyos.template import render_to_string
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
+frrender = FRRender()
+
+vrf = None
+if len(argv) > 1:
+ vrf = argv[1]
config_file = '/etc/iproute2/rt_tables.d/vyos-static.conf'
@@ -38,36 +42,17 @@ def get_config(config=None):
else:
conf = Config()
- vrf = None
- if len(argv) > 1:
- vrf = argv[1]
+ return get_frrender_dict(conf)
+
+def verify(config_dict):
+ global vrf
+ if not has_frr_protocol_in_dict(config_dict, 'static', vrf):
+ return None
- base_path = ['protocols', 'static']
# eqivalent of the C foo ? 'a' : 'b' statement
- base = vrf and ['vrf', 'name', vrf, 'protocols', 'static'] or base_path
- static = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True)
-
- # Assign the name of our VRF context
- if vrf: static['vrf'] = vrf
-
- # We also need some additional information from the config, prefix-lists
- # and route-maps for instance. They will be used in verify().
- #
- # XXX: one MUST always call this without the key_mangling() option! See
- # vyos.configverify.verify_common_route_maps() for more information.
- tmp = conf.get_config_dict(['policy'])
- # Merge policy dict into "regular" config dict
- static = dict_merge(tmp, static)
-
- # T3680 - get a list of all interfaces currently configured to use DHCP
- tmp = get_dhcp_interfaces(conf, vrf)
- if tmp: static.update({'dhcp' : tmp})
- tmp = get_pppoe_interfaces(conf, vrf)
- if tmp: static.update({'pppoe' : tmp})
-
- return static
-
-def verify(static):
+ static = vrf and config_dict['vrf']['name'][vrf]['protocols']['static'] or config_dict['static']
+ static['policy'] = config_dict['policy']
+
verify_common_route_maps(static)
for route in ['route', 'route6']:
@@ -90,33 +75,28 @@ def verify(static):
raise ConfigError(f'Can not use both blackhole and reject for '\
f'prefix "{prefix}"!')
+ if 'multicast' in static and 'route' in static['multicast']:
+ for prefix, prefix_options in static['multicast']['route'].items():
+ if not IPv4Network(prefix).is_multicast:
+ raise ConfigError(f'{prefix} is not a multicast network!')
+
return None
-def generate(static):
- if not static:
+def generate(config_dict):
+ global vrf
+ if not has_frr_protocol_in_dict(config_dict, 'static', vrf):
return None
+ # eqivalent of the C foo ? 'a' : 'b' statement
+ static = vrf and config_dict['vrf']['name'][vrf]['protocols']['static'] or config_dict['static']
+
# Put routing table names in /etc/iproute2/rt_tables
render(config_file, 'iproute2/static.conf.j2', static)
- static['new_frr_config'] = render_to_string('frr/staticd.frr.j2', static)
+ frrender.generate(config_dict)
return None
def apply(static):
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
- frr_cfg.load_configuration(frr.mgmt_daemon)
-
- if 'vrf' in static:
- vrf = static['vrf']
- frr_cfg.modify_section(f'^vrf {vrf}', stop_pattern='^exit-vrf', remove_stop_mark=True)
- else:
- frr_cfg.modify_section(r'^ip route .*')
- frr_cfg.modify_section(r'^ipv6 route .*')
-
- if 'new_frr_config' in static:
- frr_cfg.add_before(frr.default_add_before, static['new_frr_config'])
- frr_cfg.commit_configuration()
-
+ frrender.apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_static_multicast.py b/src/conf_mode/protocols_static_multicast.py
deleted file mode 100755
index 4393f3ed3..000000000
--- a/src/conf_mode/protocols_static_multicast.py
+++ /dev/null
@@ -1,133 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2020-2024 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-
-from ipaddress import IPv4Address
-from sys import exit
-
-from vyos import ConfigError
-from vyos import frr
-from vyos.config import Config
-from vyos.template import render_to_string
-
-from vyos import airbag
-airbag.enable()
-
-config_file = r'/tmp/static_mcast.frr'
-
-# Get configuration for static multicast route
-def get_config(config=None):
- if config:
- conf = config
- else:
- conf = Config()
- mroute = {
- 'old_mroute' : {},
- 'mroute' : {}
- }
-
- base_path = "protocols static multicast"
-
- if not (conf.exists(base_path) or conf.exists_effective(base_path)):
- return None
-
- conf.set_level(base_path)
-
- # Get multicast effective routes
- for route in conf.list_effective_nodes('route'):
- mroute['old_mroute'][route] = {}
- for next_hop in conf.list_effective_nodes('route {0} next-hop'.format(route)):
- mroute['old_mroute'][route].update({
- next_hop : conf.return_value('route {0} next-hop {1} distance'.format(route, next_hop))
- })
-
- # Get multicast effective interface-routes
- for route in conf.list_effective_nodes('interface-route'):
- if not route in mroute['old_mroute']:
- mroute['old_mroute'][route] = {}
- for next_hop in conf.list_effective_nodes('interface-route {0} next-hop-interface'.format(route)):
- mroute['old_mroute'][route].update({
- next_hop : conf.return_value('interface-route {0} next-hop-interface {1} distance'.format(route, next_hop))
- })
-
- # Get multicast routes
- for route in conf.list_nodes('route'):
- mroute['mroute'][route] = {}
- for next_hop in conf.list_nodes('route {0} next-hop'.format(route)):
- mroute['mroute'][route].update({
- next_hop : conf.return_value('route {0} next-hop {1} distance'.format(route, next_hop))
- })
-
- # Get multicast interface-routes
- for route in conf.list_nodes('interface-route'):
- if not route in mroute['mroute']:
- mroute['mroute'][route] = {}
- for next_hop in conf.list_nodes('interface-route {0} next-hop-interface'.format(route)):
- mroute['mroute'][route].update({
- next_hop : conf.return_value('interface-route {0} next-hop-interface {1} distance'.format(route, next_hop))
- })
-
- return mroute
-
-def verify(mroute):
- if mroute is None:
- return None
-
- for mcast_route in mroute['mroute']:
- route = mcast_route.split('/')
- if IPv4Address(route[0]) < IPv4Address('224.0.0.0'):
- raise ConfigError(f'{mcast_route} not a multicast network')
-
-
-def generate(mroute):
- if mroute is None:
- return None
-
- mroute['new_frr_config'] = render_to_string('frr/static_mcast.frr.j2', mroute)
- return None
-
-
-def apply(mroute):
- if mroute is None:
- return None
-
- frr_cfg = frr.FRRConfig()
- frr_cfg.load_configuration(frr.mgmt_daemon)
-
- if 'old_mroute' in mroute:
- for route_gr in mroute['old_mroute']:
- for nh in mroute['old_mroute'][route_gr]:
- if mroute['old_mroute'][route_gr][nh]:
- frr_cfg.modify_section(f'^ip mroute {route_gr} {nh} {mroute["old_mroute"][route_gr][nh]}')
- else:
- frr_cfg.modify_section(f'^ip mroute {route_gr} {nh}')
-
- if 'new_frr_config' in mroute:
- frr_cfg.add_before(frr.default_add_before, mroute['new_frr_config'])
-
- frr_cfg.commit_configuration()
- 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/service_snmp.py b/src/conf_mode/service_snmp.py
index 134662f85..1174b1238 100755
--- a/src/conf_mode/service_snmp.py
+++ b/src/conf_mode/service_snmp.py
@@ -34,7 +34,6 @@ from vyos.utils.process import call
from vyos.utils.permission import chmod_755
from vyos.version import get_version_data
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
@@ -261,16 +260,6 @@ def apply(snmp):
# start SNMP daemon
call(f'systemctl reload-or-restart {systemd_service}')
-
- # Enable AgentX in FRR
- # This should be done for each daemon individually because common command
- # works only if all the daemons started with SNMP support
- # Following daemons from FRR 9.0/stable have SNMP module compiled in VyOS
- frr_daemons_list = [frr.zebra_daemon, frr.bgp_daemon, frr.ospf_daemon, frr.ospf6_daemon,
- frr.rip_daemon, frr.isis_daemon, frr.ldpd_daemon]
- for frr_daemon in frr_daemons_list:
- call(f'vtysh -c "configure terminal" -d {frr_daemon} -c "agentx" >/dev/null')
-
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index 3d3845fdf..a13bb8b1e 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -19,13 +19,14 @@ from jmespath import search
from json import loads
from vyos.config import Config
+from vyos.configdict import get_frrender_dict
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.configverify import verify_route_map
from vyos.firewall import conntrack_required
+from vyos.frrender import FRRender
from vyos.ifconfig import Interface
from vyos.template import render
-from vyos.template import render_to_string
from vyos.utils.dict import dict_search
from vyos.utils.network import get_vrf_tableid
from vyos.utils.network import get_vrf_members
@@ -35,9 +36,9 @@ from vyos.utils.process import cmd
from vyos.utils.process import popen
from vyos.utils.system import sysctl_write
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
+frrender = FRRender()
config_file = '/etc/iproute2/rt_tables.d/vyos-vrf.conf'
k_mod = ['vrf']
@@ -132,6 +133,10 @@ def get_config(config=None):
if 'name' in vrf:
vrf['conntrack'] = conntrack_required(conf)
+ # We need to merge the FRR rendering dict into the VRF dict
+ # this is required to get the route-map information to FRR
+ vrf.update({'frrender' : get_frrender_dict(conf)})
+
# We also need the route-map information from the config
#
# XXX: one MUST always call this without the key_mangling() option! See
@@ -204,8 +209,9 @@ def verify(vrf):
def generate(vrf):
# Render iproute2 VR helper names
render(config_file, 'iproute2/vrf.conf.j2', vrf)
- # Render VRF Kernel/Zebra route-map filters
- vrf['frr_zebra_config'] = render_to_string('frr/zebra.vrf.route-map.frr.j2', vrf)
+
+ if 'frrender' in vrf:
+ frrender.generate(vrf['frrender'])
return None
@@ -347,13 +353,8 @@ def apply(vrf):
if has_rule(afi, 2000, 'l3mdev'):
call(f'ip {afi} rule del pref 2000 l3mdev unreachable')
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
- frr_cfg.load_configuration(frr.mgmt_daemon)
- frr_cfg.modify_section(f'^vrf .+', stop_pattern='^exit-vrf', remove_stop_mark=True)
- if 'frr_zebra_config' in vrf:
- frr_cfg.add_before(frr.default_add_before, vrf['frr_zebra_config'])
- frr_cfg.commit_configuration()
+ if 'frrender' in vrf:
+ frrender.apply()
return None