summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/accel-ppp/chap-secrets.ipoe.j22
-rwxr-xr-xdata/templates/firewall/nftables.j23
-rw-r--r--data/templates/frr/babeld.frr.j22
-rw-r--r--data/templates/frr/bgpd.frr.j224
-rw-r--r--data/templates/frr/distribute_list_macro.j23
-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/ipv6_distribute_list_macro.j23
-rw-r--r--data/templates/frr/ospfd.frr.j27
-rw-r--r--data/templates/frr/pim6d.frr.j221
-rw-r--r--data/templates/frr/pimd.frr.j236
-rw-r--r--data/templates/frr/rpki.frr.j24
-rw-r--r--data/templates/frr/static_mcast.frr.j211
-rw-r--r--data/templates/frr/static_routes_macro.j229
-rw-r--r--data/templates/frr/staticd.frr.j2103
-rw-r--r--data/templates/frr/zebra.route-map.frr.j22
-rw-r--r--data/templates/frr/zebra.vrf.route-map.frr.j22
-rw-r--r--data/templates/login/tacplus_nss.conf.j25
-rw-r--r--debian/control2
-rwxr-xr-xdebian/rules2
-rw-r--r--debian/vyos-1x.postinst5
-rw-r--r--interface-definitions/include/firewall/global-options.xml.i2
-rw-r--r--interface-definitions/include/ospf/protocol-common-config.xml.i2
-rw-r--r--interface-definitions/include/ospf/retransmit-window.xml.i15
-rw-r--r--interface-definitions/include/qos/class-match.xml.i50
-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/static-route-bfd.xml.i36
-rw-r--r--interface-definitions/include/static/static-route.xml.i20
-rw-r--r--interface-definitions/include/static/static-route6.xml.i17
-rw-r--r--interface-definitions/include/version/quagga-version.xml.i2
-rw-r--r--interface-definitions/protocols_static.xml.in49
-rw-r--r--interface-definitions/protocols_static_multicast.xml.in95
-rw-r--r--interface-definitions/service_ipoe-server.xml.in12
-rw-r--r--op-mode-definitions/monitor-log.xml.in12
-rwxr-xr-xop-mode-definitions/show-log.xml.in14
-rw-r--r--python/vyos/configdict.py495
-rw-r--r--python/vyos/configverify.py18
-rw-r--r--python/vyos/defaults.py1
-rw-r--r--python/vyos/frr.py551
-rw-r--r--python/vyos/frrender.py181
-rw-r--r--python/vyos/qos/base.py148
-rw-r--r--python/vyos/qos/priority.py19
-rw-r--r--python/vyos/utils/process.py4
-rw-r--r--smoketest/config-tests/static-route-basic37
-rw-r--r--smoketest/configs/static-route-basic148
-rw-r--r--smoketest/scripts/cli/base_vyostest_shim.py22
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py1
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_bonding.py5
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_ethernet.py3
-rwxr-xr-xsmoketest/scripts/cli/test_policy.py8
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_babel.py16
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bfd.py18
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bgp.py40
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_isis.py42
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_mpls.py10
-rw-r--r--smoketest/scripts/cli/test_protocols_openfabric.py26
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_ospf.py88
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_ospfv3.py41
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_pim.py44
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_pim6.py30
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_rip.py9
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_ripng.py9
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_rpki.py16
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_segment-routing.py8
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_static.py109
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_static_multicast.py49
-rwxr-xr-xsmoketest/scripts/cli/test_qos.py142
-rwxr-xr-xsmoketest/scripts/cli/test_service_ipoe-server.py35
-rwxr-xr-xsmoketest/scripts/cli/test_system_ip.py62
-rwxr-xr-xsmoketest/scripts/cli/test_system_ipv6.py62
-rwxr-xr-xsmoketest/scripts/cli/test_vrf.py35
-rwxr-xr-xsrc/conf_mode/interfaces_bonding.py38
-rwxr-xr-xsrc/conf_mode/interfaces_ethernet.py37
-rwxr-xr-xsrc/conf_mode/policy.py134
-rwxr-xr-xsrc/conf_mode/protocols_babel.py81
-rwxr-xr-xsrc/conf_mode/protocols_bfd.py43
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py151
-rwxr-xr-xsrc/conf_mode/protocols_eigrp.py93
-rwxr-xr-xsrc/conf_mode/protocols_isis.py105
-rwxr-xr-xsrc/conf_mode/protocols_mpls.py46
-rw-r--r--src/conf_mode/protocols_openfabric.py67
-rwxr-xr-xsrc/conf_mode/protocols_ospf.py135
-rwxr-xr-xsrc/conf_mode/protocols_ospfv3.py125
-rwxr-xr-xsrc/conf_mode/protocols_pim.py107
-rwxr-xr-xsrc/conf_mode/protocols_pim6.py71
-rwxr-xr-xsrc/conf_mode/protocols_rip.py82
-rwxr-xr-xsrc/conf_mode/protocols_ripng.py67
-rwxr-xr-xsrc/conf_mode/protocols_rpki.py53
-rwxr-xr-xsrc/conf_mode/protocols_segment-routing.py96
-rwxr-xr-xsrc/conf_mode/protocols_static.py89
-rwxr-xr-xsrc/conf_mode/protocols_static_multicast.py135
-rwxr-xr-xsrc/conf_mode/service_snmp.py9
-rwxr-xr-xsrc/conf_mode/system_ip.py71
-rwxr-xr-xsrc/conf_mode/system_ipv6.py71
-rwxr-xr-xsrc/conf_mode/system_login.py15
-rwxr-xr-xsrc/conf_mode/vrf.py48
-rwxr-xr-xsrc/helpers/latest-image-url.py21
-rwxr-xr-xsrc/init/vyos-router3
-rw-r--r--src/migration-scripts/dns-dynamic/1-to-233
-rw-r--r--src/migration-scripts/quagga/11-to-1275
-rwxr-xr-xsrc/op_mode/image_installer.py28
-rwxr-xr-xsrc/services/vyos-configd8
-rw-r--r--src/tests/test_initial_setup.py4
-rw-r--r--src/validators/ether-type37
105 files changed, 2653 insertions, 2697 deletions
diff --git a/data/templates/accel-ppp/chap-secrets.ipoe.j2 b/data/templates/accel-ppp/chap-secrets.ipoe.j2
index 43083e22e..dd85160c0 100644
--- a/data/templates/accel-ppp/chap-secrets.ipoe.j2
+++ b/data/templates/accel-ppp/chap-secrets.ipoe.j2
@@ -6,7 +6,7 @@
{% if mac_config.vlan is vyos_defined %}
{% set iface = iface ~ '.' ~ mac_config.vlan %}
{% endif %}
-{{ "%-11s" | format(iface) }} * {{ mac | lower }} * {{ mac_config.rate_limit.download ~ '/' ~ mac_config.rate_limit.upload if mac_config.rate_limit.download is vyos_defined and mac_config.rate_limit.upload is vyos_defined }}
+{{ "%-11s" | format(iface) }} * {{ mac | lower }} {{ mac_config.static_ip if mac_config.static_ip is vyos_defined else '*' }} {{ mac_config.rate_limit.download ~ '/' ~ mac_config.rate_limit.upload if mac_config.rate_limit.download is vyos_defined and mac_config.rate_limit.upload is vyos_defined }}
{% endfor %}
{% endif %}
{% endfor %}
diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2
index 034328400..a35143870 100755
--- a/data/templates/firewall/nftables.j2
+++ b/data/templates/firewall/nftables.j2
@@ -382,6 +382,7 @@ table bridge vyos_filter {
{% if 'invalid_connections' in global_options.apply_to_bridged_traffic %}
ct state invalid udp sport 67 udp dport 68 counter accept
ct state invalid ether type arp counter accept
+ ct state invalid ether type 0x8864 counter accept
{% endif %}
{% endif %}
{% if global_options.state_policy is vyos_defined %}
@@ -445,4 +446,4 @@ table bridge vyos_filter {
return
}
{% endif %}
-} \ No newline at end of file
+}
diff --git a/data/templates/frr/babeld.frr.j2 b/data/templates/frr/babeld.frr.j2
index 344a5f988..292bd9972 100644
--- a/data/templates/frr/babeld.frr.j2
+++ b/data/templates/frr/babeld.frr.j2
@@ -45,7 +45,6 @@ exit
{% endfor %}
{% endif %}
!
-{# Babel configuration #}
router babel
{% if parameters.diversity is vyos_defined %}
babel diversity
@@ -82,4 +81,3 @@ router babel
{% endif %}
exit
!
-end
diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2
index e5bfad59d..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() %}
@@ -245,9 +251,11 @@
neighbor {{ neighbor }} activate
exit-address-family
!
+{# j2lint: disable=jinja-statements-delimeter #}
{% endfor %}
{% endif %}
-{% endmacro %}
+{# j2lint: disable=jinja-statements-delimeter #}
+{%- endmacro -%}
!
router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
{% if parameters.ebgp_requires_policy is vyos_defined %}
@@ -512,13 +520,15 @@ router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
{% if peer_group is vyos_defined %}
{% for peer, config in peer_group.items() %}
{{ bgp_neighbor(peer, config, true) }}
-{% endfor %}
+{# j2lint: disable=jinja-statements-delimeter #}
+{%- endfor %}
{% endif %}
!
{% if neighbor is vyos_defined %}
{% for peer, config in neighbor.items() %}
{{ bgp_neighbor(peer, config) }}
-{% endfor %}
+{# j2lint: disable=jinja-statements-delimeter #}
+{%- endfor %}
{% endif %}
!
{% if listen.limit is vyos_defined %}
diff --git a/data/templates/frr/distribute_list_macro.j2 b/data/templates/frr/distribute_list_macro.j2
index c10bf732d..3e15ef100 100644
--- a/data/templates/frr/distribute_list_macro.j2
+++ b/data/templates/frr/distribute_list_macro.j2
@@ -27,4 +27,5 @@
{% if distribute_list.prefix_list.out is vyos_defined %}
distribute-list prefix {{ distribute_list.prefix_list.out }} out
{% endif %}
-{% endmacro %}
+{# j2lint: disable=jinja-statements-delimeter #}
+{%- endmacro -%}
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/ipv6_distribute_list_macro.j2 b/data/templates/frr/ipv6_distribute_list_macro.j2
index c365fbdae..2f483b7d4 100644
--- a/data/templates/frr/ipv6_distribute_list_macro.j2
+++ b/data/templates/frr/ipv6_distribute_list_macro.j2
@@ -27,4 +27,5 @@
{% if distribute_list.prefix_list.out is vyos_defined %}
ipv6 distribute-list prefix {{ distribute_list.prefix_list.out }} out
{% endif %}
-{% endmacro %}
+{# j2lint: disable=jinja-statements-delimeter #}
+{%- endmacro -%}
diff --git a/data/templates/frr/ospfd.frr.j2 b/data/templates/frr/ospfd.frr.j2
index ab074b6a2..bc2c74b10 100644
--- a/data/templates/frr/ospfd.frr.j2
+++ b/data/templates/frr/ospfd.frr.j2
@@ -30,6 +30,9 @@ interface {{ iface }}
{% if iface_config.retransmit_interval is vyos_defined %}
ip ospf retransmit-interval {{ iface_config.retransmit_interval }}
{% endif %}
+{% if iface_config.retransmit_window is vyos_defined %}
+ ip ospf retransmit-window {{ iface_config.retransmit_window }}
+{% endif %}
{% if iface_config.transmit_delay is vyos_defined %}
ip ospf transmit-delay {{ iface_config.transmit_delay }}
{% endif %}
@@ -125,7 +128,7 @@ router ospf {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
{% endfor %}
{% endif %}
{# The following values are default values #}
- area {{ area_id }} virtual-link {{ link }} hello-interval {{ link_config.hello_interval }} retransmit-interval {{ link_config.retransmit_interval }} transmit-delay {{ link_config.transmit_delay }} dead-interval {{ link_config.dead_interval }}
+ area {{ area_id }} virtual-link {{ link }} hello-interval {{ link_config.hello_interval }} retransmit-interval {{ link_config.retransmit_interval }} retransmit-window {{ link_config.retransmit_window }} transmit-delay {{ link_config.transmit_delay }} dead-interval {{ link_config.dead_interval }}
{% endfor %}
{% endif %}
{% endfor %}
@@ -233,6 +236,7 @@ router ospf {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
{% endfor %}
{% endif %}
{% if segment_routing is vyos_defined %}
+ segment-routing on
{% if segment_routing.maximum_label_depth is vyos_defined %}
segment-routing node-msd {{ segment_routing.maximum_label_depth }}
{% endif %}
@@ -252,7 +256,6 @@ router ospf {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
{% endif %}
{% endfor %}
{% endif %}
- segment-routing on
{% endif %}
{% if timers.throttle.spf.delay is vyos_defined and timers.throttle.spf.initial_holdtime is vyos_defined and timers.throttle.spf.max_holdtime is vyos_defined %}
{# Timer values have default values #}
diff --git a/data/templates/frr/pim6d.frr.j2 b/data/templates/frr/pim6d.frr.j2
index bac716fcc..d4144a2f9 100644
--- a/data/templates/frr/pim6d.frr.j2
+++ b/data/templates/frr/pim6d.frr.j2
@@ -40,10 +40,10 @@ interface {{ iface }}
{% for group, group_config in iface_config.mld.join.items() %}
{% if group_config.source is vyos_defined %}
{% for source in group_config.source %}
- ipv6 mld join {{ group }} {{ source }}
+ ipv6 mld join-group {{ group }} {{ source }}
{% endfor %}
{% else %}
- ipv6 mld join {{ group }}
+ ipv6 mld join-group {{ group }}
{% endif %}
{% endfor %}
{% endif %}
@@ -52,30 +52,33 @@ exit
{% endfor %}
{% endif %}
!
+router pim6
{% if join_prune_interval is vyos_defined %}
-ipv6 pim join-prune-interval {{ join_prune_interval }}
+ join-prune-interval {{ join_prune_interval }}
{% endif %}
{% if keep_alive_timer is vyos_defined %}
-ipv6 pim keep-alive-timer {{ keep_alive_timer }}
+ keep-alive-timer {{ keep_alive_timer }}
{% endif %}
{% if packets is vyos_defined %}
-ipv6 pim packets {{ packets }}
+ packets {{ packets }}
{% endif %}
{% if register_suppress_time is vyos_defined %}
-ipv6 pim register-suppress-time {{ register_suppress_time }}
+ register-suppress-time {{ register_suppress_time }}
{% endif %}
{% if rp.address is vyos_defined %}
{% for address, address_config in rp.address.items() %}
{% if address_config.group is vyos_defined %}
{% for group in address_config.group %}
-ipv6 pim rp {{ address }} {{ group }}
+ rp {{ address }} {{ group }}
{% endfor %}
{% endif %}
{% if address_config.prefix_list6 is vyos_defined %}
-ipv6 pim rp {{ address }} prefix-list {{ address_config.prefix_list6 }}
+ rp {{ address }} prefix-list {{ address_config.prefix_list6 }}
{% endif %}
{% endfor %}
{% endif %}
{% if rp.keep_alive_timer is vyos_defined %}
-ipv6 pim rp keep-alive-timer {{ rp.keep_alive_timer }}
+ rp keep-alive-timer {{ rp.keep_alive_timer }}
{% endif %}
+exit
+!
diff --git a/data/templates/frr/pimd.frr.j2 b/data/templates/frr/pimd.frr.j2
index 68edf4a5c..d474d8495 100644
--- a/data/templates/frr/pimd.frr.j2
+++ b/data/templates/frr/pimd.frr.j2
@@ -39,10 +39,10 @@ interface {{ iface }}
{% for join, join_config in iface_config.igmp.join.items() %}
{% if join_config.source_address is vyos_defined %}
{% for source_address in join_config.source_address %}
- ip igmp join {{ join }} {{ source_address }}
+ ip igmp join-group {{ join }} {{ source_address }}
{% endfor %}
{% else %}
- ip igmp join {{ join }}
+ ip igmp join-group {{ join }}
{% endif %}
{% endfor %}
{% endif %}
@@ -51,45 +51,47 @@ exit
{% endfor %}
{% endif %}
!
+{% if igmp.watermark_warning is vyos_defined %}
+ip igmp watermark-warn {{ igmp.watermark_warning }}
+{% endif %}
+!
+router pim
{% if ecmp is vyos_defined %}
-ip pim ecmp {{ 'rebalance' if ecmp.rebalance is vyos_defined }}
+ ecmp {{ 'rebalance' if ecmp.rebalance is vyos_defined }}
{% endif %}
{% if join_prune_interval is vyos_defined %}
-ip pim join-prune-interval {{ join_prune_interval }}
+ join-prune-interval {{ join_prune_interval }}
{% endif %}
{% if keep_alive_timer is vyos_defined %}
-ip pim keep-alive-timer {{ keep_alive_timer }}
+ keep-alive-timer {{ keep_alive_timer }}
{% endif %}
{% if packets is vyos_defined %}
-ip pim packets {{ packets }}
+ packets {{ packets }}
{% endif %}
{% if register_accept_list.prefix_list is vyos_defined %}
-ip pim register-accept-list {{ register_accept_list.prefix_list }}
+ register-accept-list {{ register_accept_list.prefix_list }}
{% endif %}
{% if register_suppress_time is vyos_defined %}
-ip pim register-suppress-time {{ register_suppress_time }}
+ register-suppress-time {{ register_suppress_time }}
{% endif %}
{% if rp.address is vyos_defined %}
{% for address, address_config in rp.address.items() %}
{% for group in address_config.group %}
-ip pim rp {{ address }} {{ group }}
+ rp {{ address }} {{ group }}
{% endfor %}
{% endfor %}
{% endif %}
{% if rp.keep_alive_timer is vyos_defined %}
-ip pim rp keep-alive-timer {{ rp.keep_alive_timer }}
+ rp keep-alive-timer {{ rp.keep_alive_timer }}
{% endif %}
{% if no_v6_secondary is vyos_defined %}
-no ip pim send-v6-secondary
+ no send-v6-secondary
{% endif %}
{% if spt_switchover.infinity_and_beyond is vyos_defined %}
-ip pim spt-switchover infinity-and-beyond {{ 'prefix-list ' ~ spt_switchover.infinity_and_beyond.prefix_list if spt_switchover.infinity_and_beyond.prefix_list is defined }}
+ spt-switchover infinity-and-beyond {{ 'prefix-list ' ~ spt_switchover.infinity_and_beyond.prefix_list if spt_switchover.infinity_and_beyond.prefix_list is defined }}
{% endif %}
{% if ssm.prefix_list is vyos_defined %}
-ip pim ssm prefix-list {{ ssm.prefix_list }}
-{% endif %}
-!
-{% if igmp.watermark_warning is vyos_defined %}
-ip igmp watermark-warn {{ igmp.watermark_warning }}
+ ssm prefix-list {{ ssm.prefix_list }}
{% endif %}
+exit
!
diff --git a/data/templates/frr/rpki.frr.j2 b/data/templates/frr/rpki.frr.j2
index 59724102c..59d5bf0ac 100644
--- a/data/templates/frr/rpki.frr.j2
+++ b/data/templates/frr/rpki.frr.j2
@@ -5,9 +5,9 @@ rpki
{% for peer, peer_config in cache.items() %}
{# port is mandatory and preference uses a default value #}
{% if peer_config.ssh.username is vyos_defined %}
- rpki cache {{ peer | replace('_', '-') }} {{ peer_config.port }} {{ peer_config.ssh.username }} {{ peer_config.ssh.private_key_file }} {{ peer_config.ssh.public_key_file }} preference {{ peer_config.preference }}
+ rpki cache ssh {{ peer | replace('_', '-') }} {{ peer_config.port }} {{ peer_config.ssh.username }} {{ peer_config.ssh.private_key_file }} {{ peer_config.ssh.public_key_file }} preference {{ peer_config.preference }}
{% else %}
- rpki cache {{ peer | replace('_', '-') }} {{ peer_config.port }} preference {{ peer_config.preference }}
+ rpki cache tcp {{ peer | replace('_', '-') }} {{ peer_config.port }} preference {{ peer_config.preference }}
{% endif %}
{% endfor %}
{% endif %}
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 cf8046968..000000000
--- a/data/templates/frr/static_routes_macro.j2
+++ /dev/null
@@ -1,29 +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 %}
-{% set next_hop = prefix_config.dhcp_interface | get_dhcp_router %}
-{% if next_hop is vyos_defined %}
-{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ prefix_config.dhcp_interface }} {{ 'table ' ~ table if table is vyos_defined }}
-{% endif %}
-{% 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 992a0435c..90d17ec14 100644
--- a/data/templates/frr/staticd.frr.j2
+++ b/data/templates/frr/staticd.frr.j2
@@ -1,19 +1,85 @@
-{% 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.multi_hop.source_address is vyos_defined %}
+{% set ip_route = ip_route ~ ' source ' ~ config.bfd.multi_hop.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 #}
{% if route is vyos_defined %}
{% for prefix, prefix_config in route.items() %}
{{ static_routes(ip_prefix, prefix, prefix_config) }}
-{% endfor %}
+{# j2lint: disable=jinja-statements-delimeter #}
+{%- endfor %}
{% endif %}
{# IPv4 default routes from DHCP interfaces #}
{% if dhcp is vyos_defined %}
@@ -28,13 +94,14 @@ vrf {{ vrf }}
{% if pppoe is vyos_defined %}
{% for interface, interface_config in pppoe.items() if interface_config.no_default_route is not vyos_defined %}
{{ ip_prefix }} route 0.0.0.0/0 {{ interface }} tag 210 {{ interface_config.default_route_distance if interface_config.default_route_distance is vyos_defined }}
-{% endfor %}
+{%- endfor %}
{% endif %}
{# IPv6 routing #}
{% if route6 is vyos_defined %}
{% for prefix, prefix_config in route6.items() %}
{{ static_routes(ipv6_prefix, prefix, prefix_config) }}
-{% endfor %}
+{# j2lint: disable=jinja-statements-delimeter #}
+{%- endfor %}
{% endif %}
{% if vrf is vyos_defined %}
exit-vrf
@@ -45,19 +112,31 @@ 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 mroute is vyos_defined %}
+{% set ip_prefix = 'ip m' %}
+{# IPv4 multicast routing #}
+{% for prefix, prefix_config in mroute.items() %}
+{{ static_routes(ip_prefix, prefix, prefix_config) }}
+{# j2lint: disable=jinja-statements-delimeter #}
+{%- endfor %}
+{% endif %}
+!
{% if route_map is vyos_defined %}
ip protocol static route-map {{ route_map }}
!
diff --git a/data/templates/frr/zebra.route-map.frr.j2 b/data/templates/frr/zebra.route-map.frr.j2
index 669d58354..70a810f43 100644
--- a/data/templates/frr/zebra.route-map.frr.j2
+++ b/data/templates/frr/zebra.route-map.frr.j2
@@ -1,4 +1,6 @@
!
+{{ 'no ' if disable_forwarding is vyos_defined }}{{ afi }} forwarding
+!
{% if nht.no_resolve_via_default is vyos_defined %}
no {{ afi }} nht resolve-via-default
{% endif %}
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/data/templates/login/tacplus_nss.conf.j2 b/data/templates/login/tacplus_nss.conf.j2
index 2a30b1710..1c5402233 100644
--- a/data/templates/login/tacplus_nss.conf.j2
+++ b/data/templates/login/tacplus_nss.conf.j2
@@ -21,7 +21,7 @@
# Cumulus Linux ships with it set to 1001, so we never lookup our standard
# local users, including the cumulus uid of 1000. Should not be greater
# than the local tacacs{0..15} uids
-min_uid=900
+min_uid={{ tacacs_min_uid }}
# This is a comma separated list of usernames that are never sent to
# a tacacs server, they cause an early not found return.
@@ -30,7 +30,7 @@ min_uid=900
# that during pathname completion, bash can do an NSS lookup on "*"
# To avoid server round trip delays, or worse, unreachable server delays
# on filename completion, we include "*" in the exclusion list.
-exclude_users=root,telegraf,radvd,strongswan,tftp,conservr,frr,ocserv,pdns,_chrony,_lldpd,sshd,openvpn,radius_user,radius_priv_user,*{{ ',' + user | join(',') if user is vyos_defined }}
+exclude_users=*{{ ',' + exclude_users | join(',') if exclude_users is vyos_defined }}
# The include keyword allows centralizing the tacacs+ server information
# including the IP address and shared secret
@@ -71,4 +71,3 @@ source_ip={{ tacacs.source_address }}
# as in tacplus_servers, since tacplus_servers should not be readable
# by users other than root.
timeout={{ tacacs.timeout }}
-
diff --git a/debian/control b/debian/control
index a19461412..76ca83dcd 100644
--- a/debian/control
+++ b/debian/control
@@ -166,7 +166,7 @@ Depends:
sstp-client,
# End "interfaces sstpc"
# For "protocols *"
- frr (>= 9.1),
+ frr (>= 10.2),
frr-pythontools,
frr-rpki-rtrlib,
frr-snmp,
diff --git a/debian/rules b/debian/rules
index c15fcab11..d7c427b0d 100755
--- a/debian/rules
+++ b/debian/rules
@@ -94,6 +94,8 @@ override_dh_auto_install:
cp -r data/reftree.cache $(DIR)/$(VYCONF_CONFIG_DIR)
mkdir -p $(DIR)/$(VYOS_DATA_DIR)
cp -r data/* $(DIR)/$(VYOS_DATA_DIR)
+ # Remove j2lint comments / linter configuration which would insert additional new-lines
+ find $(DIR)/$(VYOS_DATA_DIR) -name "*.j2" -type f | xargs sed -i -e '/^{#.*#}/d'
# Create localui dir
mkdir -p $(DIR)/$(VYOS_LOCALUI_DIR)
diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst
index d83634cfc..ff5a91e09 100644
--- a/debian/vyos-1x.postinst
+++ b/debian/vyos-1x.postinst
@@ -267,3 +267,8 @@ fi
# T4287 - as we have a non-signed kernel use the upstream wireless reulatory database
update-alternatives --set regulatory.db /lib/firmware/regulatory.db-upstream
+
+# Restart vyos-configd to apply changes in Python scripts/templates
+if systemctl is-active --quiet vyos-configd; then
+ systemctl restart vyos-configd
+fi
diff --git a/interface-definitions/include/firewall/global-options.xml.i b/interface-definitions/include/firewall/global-options.xml.i
index 05fdd75cb..355b41fde 100644
--- a/interface-definitions/include/firewall/global-options.xml.i
+++ b/interface-definitions/include/firewall/global-options.xml.i
@@ -51,7 +51,7 @@
<children>
<leafNode name="invalid-connections">
<properties>
- <help>Accept ARP and DHCP despite they are marked as invalid connection</help>
+ <help>Accept ARP, DHCP and PPPoE despite they are marked as invalid connection</help>
<valueless/>
</properties>
</leafNode>
diff --git a/interface-definitions/include/ospf/protocol-common-config.xml.i b/interface-definitions/include/ospf/protocol-common-config.xml.i
index c4778e126..cef832381 100644
--- a/interface-definitions/include/ospf/protocol-common-config.xml.i
+++ b/interface-definitions/include/ospf/protocol-common-config.xml.i
@@ -321,6 +321,7 @@
<children>
#include <include/ospf/authentication.xml.i>
#include <include/ospf/intervals.xml.i>
+ #include <include/ospf/retransmit-window.xml.i>
</children>
</tagNode>
</children>
@@ -433,6 +434,7 @@
</leafNode>
#include <include/ospf/authentication.xml.i>
#include <include/ospf/intervals.xml.i>
+ #include <include/ospf/retransmit-window.xml.i>
#include <include/ospf/interface-common.xml.i>
#include <include/isis/ldp-sync-interface.xml.i>
<leafNode name="bandwidth">
diff --git a/interface-definitions/include/ospf/retransmit-window.xml.i b/interface-definitions/include/ospf/retransmit-window.xml.i
new file mode 100644
index 000000000..a5e20f522
--- /dev/null
+++ b/interface-definitions/include/ospf/retransmit-window.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from ospf/retransmit-window.xml.i -->
+<leafNode name="retransmit-window">
+ <properties>
+ <help>Window for LSA retransmit</help>
+ <valueHelp>
+ <format>u32:20-1000</format>
+ <description>Retransmit LSAs expiring in this window (milliseconds)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 20-1000"/>
+ </constraint>
+ </properties>
+ <defaultValue>50</defaultValue>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/qos/class-match.xml.i b/interface-definitions/include/qos/class-match.xml.i
index 77d1933a3..3ad5547f2 100644
--- a/interface-definitions/include/qos/class-match.xml.i
+++ b/interface-definitions/include/qos/class-match.xml.i
@@ -29,12 +29,12 @@
<leafNode name="protocol">
<properties>
<help>Ethernet protocol for this match</help>
- <!-- this refers to /etc/protocols -->
+ <!-- this refers to /etc/ethertypes -->
<completionHelp>
<list>all 802.1Q 802_2 802_3 aarp aoe arp atalk dec ip ipv6 ipx lat localtalk rarp snap x25</list>
</completionHelp>
<valueHelp>
- <format>u32:0-65535</format>
+ <format>u32:1-65535</format>
<description>Ethernet protocol number</description>
</valueHelp>
<valueHelp>
@@ -50,7 +50,7 @@
<description>Internet IP (IPv4)</description>
</valueHelp>
<valueHelp>
- <format>ipv6</format>
+ <format>_ipv6</format>
<description>Internet IP (IPv6)</description>
</valueHelp>
<valueHelp>
@@ -59,7 +59,7 @@
</valueHelp>
<valueHelp>
<format>atalk</format>
- <description>Appletalk</description>
+ <description>AppleTalk</description>
</valueHelp>
<valueHelp>
<format>ipx</format>
@@ -69,8 +69,48 @@
<format>802.1Q</format>
<description>802.1Q VLAN tag</description>
</valueHelp>
+ <valueHelp>
+ <format>802_2</format>
+ <description>IEEE 802.2</description>
+ </valueHelp>
+ <valueHelp>
+ <format>802_3</format>
+ <description>IEEE 802.3</description>
+ </valueHelp>
+ <valueHelp>
+ <format>aarp</format>
+ <description>AppleTalk Address Resolution Protocol</description>
+ </valueHelp>
+ <valueHelp>
+ <format>aoe</format>
+ <description>ATA over Ethernet</description>
+ </valueHelp>
+ <valueHelp>
+ <format>dec</format>
+ <description>DECnet Protocol</description>
+ </valueHelp>
+ <valueHelp>
+ <format>lat</format>
+ <description>Local Area Transport</description>
+ </valueHelp>
+ <valueHelp>
+ <format>localtalk</format>
+ <description>Apple LocalTalk</description>
+ </valueHelp>
+ <valueHelp>
+ <format>rarp</format>
+ <description>Reverse Address Resolution Protocol</description>
+ </valueHelp>
+ <valueHelp>
+ <format>snap</format>
+ <description>Subnetwork Access Protocol</description>
+ </valueHelp>
+ <valueHelp>
+ <format>x25</format>
+ <description>X.25 Packet-Switching Protocol</description>
+ </valueHelp>
<constraint>
- <validator name="ip-protocol"/>
+ <validator name="ether-type"/>
</constraint>
</properties>
</leafNode>
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/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 29921a731..fd7366286 100644
--- a/interface-definitions/include/static/static-route.xml.i
+++ b/interface-definitions/include/static/static-route.xml.i
@@ -13,7 +13,7 @@
<children>
#include <include/static/static-route-blackhole.xml.i>
#include <include/static/static-route-reject.xml.i>
- #include <include/dhcp-interface.xml.i>
+ #include <include/dhcp-interface-multi.xml.i>
#include <include/generic-description.xml.i>
<tagNode name="interface">
<properties>
@@ -51,10 +51,24 @@
#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>
+ <node name="multi-hop">
+ <properties>
+ <help>Configure BFD multi-hop session</help>
+ </properties>
+ <children>
+ #include <include/source-address-ipv4.xml.i>
+ </children>
+ </node>
+ </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..6fcc18b8a 100644
--- a/interface-definitions/include/static/static-route6.xml.i
+++ b/interface-definitions/include/static/static-route6.xml.i
@@ -48,11 +48,26 @@
</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>
+ <node name="multi-hop">
+ <properties>
+ <help>Configure BFD multi-hop session</help>
+ </properties>
+ <children>
+ #include <include/source-address-ipv6.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/include/version/quagga-version.xml.i b/interface-definitions/include/version/quagga-version.xml.i
index 23d884cd4..10ca2816e 100644
--- a/interface-definitions/include/version/quagga-version.xml.i
+++ b/interface-definitions/include/version/quagga-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/quagga-version.xml.i -->
-<syntaxVersion component='quagga' version='11'></syntaxVersion>
+<syntaxVersion component='quagga' version='12'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/protocols_static.xml.in b/interface-definitions/protocols_static.xml.in
index ca4ca2d74..d8e0ee56b 100644
--- a/interface-definitions/protocols_static.xml.in
+++ b/interface-definitions/protocols_static.xml.in
@@ -11,6 +11,55 @@
<priority>480</priority>
</properties>
<children>
+ <tagNode name="mroute">
+ <properties>
+ <help>Static IPv4 route for Multicast RIB</help>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>Network</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-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>
#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/interface-definitions/service_ipoe-server.xml.in b/interface-definitions/service_ipoe-server.xml.in
index 39cfb7889..6cc4471af 100644
--- a/interface-definitions/service_ipoe-server.xml.in
+++ b/interface-definitions/service_ipoe-server.xml.in
@@ -70,6 +70,18 @@
<constraintErrorMessage>VLAN IDs need to be in range 1-4094</constraintErrorMessage>
</properties>
</leafNode>
+ <leafNode name="static-ip">
+ <properties>
+ <help>Static client IP address</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
</children>
</tagNode>
</children>
diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in
index 6a2b7e53b..f6b70be32 100644
--- a/op-mode-definitions/monitor-log.xml.in
+++ b/op-mode-definitions/monitor-log.xml.in
@@ -114,6 +114,12 @@
</properties>
<command>journalctl --no-hostname --boot --follow --unit uacctd.service</command>
</leafNode>
+ <leafNode name="frr">
+ <properties>
+ <help>Monitor last lines of FRRouting suite log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --unit frr.service</command>
+ </leafNode>
<leafNode name="ipoe-server">
<properties>
<help>Monitor last lines of IP over Ethernet server log</help>
@@ -365,6 +371,12 @@
</properties>
<command>journalctl --no-hostname --boot --follow --unit keepalived.service</command>
</leafNode>
+ <leafNode name="vyos-configd">
+ <properties>
+ <help>Monitor last lines of VyOS configuration daemon log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --unit vyos-configd.service</command>
+ </leafNode>
<node name="wireless">
<properties>
<help>Monitor last lines of Wireless interface log</help>
diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in
index c2504686d..9dcebb6af 100755
--- a/op-mode-definitions/show-log.xml.in
+++ b/op-mode-definitions/show-log.xml.in
@@ -533,6 +533,12 @@
</properties>
<command>journalctl --no-hostname --boot --unit uacctd.service</command>
</leafNode>
+ <leafNode name="frr">
+ <properties>
+ <help>Show log for FRRouting suite</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --unit frr.service</command>
+ </leafNode>
<leafNode name="https">
<properties>
<help>Show log for HTTPs</help>
@@ -836,7 +842,7 @@
</node>
<leafNode name="vpn">
<properties>
- <help>Monitor last lines of ALL Virtual Private Network services</help>
+ <help>Show log for ALL Virtual Private Network services</help>
</properties>
<command>journalctl --no-hostname --boot --unit strongswan.service --unit accel-ppp@*.service --unit ocserv.service</command>
</leafNode>
@@ -893,6 +899,12 @@
</properties>
<command>journalctl --no-hostname --boot --unit keepalived.service</command>
</leafNode>
+ <leafNode name="vyos-configd">
+ <properties>
+ <help>Show log for VyOS configuration daemon</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --unit vyos-configd.service</command>
+ </leafNode>
<node name="wireless">
<properties>
<help>Show log for Wireless interface</help>
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 5a353b110..cbcbf9f72 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -19,6 +19,7 @@ A library for retrieving value dicts from VyOS configs in a declarative fashion.
import os
import json
+from vyos.defaults import frr_debug_enable
from vyos.utils.dict import dict_search
from vyos.utils.process import cmd
@@ -664,3 +665,497 @@ 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, argv=None) -> 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 = {}
+
+ if argv and len(argv) > 1:
+ dict['vrf_context'] = argv[1]
+
+ 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
+
+ # Zebra prefix exchange for Kernel IP/IPv6 and routing protocols
+ for ip_version in ['ip', 'ipv6']:
+ ip_cli_path = ['system', ip_version]
+ ip_dict = conf.get_config_dict(ip_cli_path, key_mangling=('-', '_'),
+ get_first_key=True, with_recursive_defaults=True)
+ if ip_dict:
+ ip_dict['afi'] = ip_version
+ dict.update({ip_version : ip_dict})
+
+ # 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)
+ rpki_ssh_key_base = '/run/frr/id_rpki'
+ for cache, cache_config in rpki.get('cache',{}).items():
+ if 'ssh' in cache_config:
+ cache_config['ssh']['public_key_file'] = f'{rpki_ssh_key_base}_{cache}.pub'
+ cache_config['ssh']['private_key_file'] = f'{rpki_ssh_key_base}_{cache}'
+ 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} })
+
+ if 'protocols' not in vrf['name'][vrf_name]:
+ vrf['name'][vrf_name].update({'protocols': {'bgp' : tmp}})
+ else:
+ 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(vrf_vni_path):
+ vrf_config.update({'vni': conf.return_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})
+
+ if os.path.exists(frr_debug_enable):
+ print('======== < BEGIN > ==========')
+ import pprint
+ pprint.pprint(dict)
+ print('========= < END > ===========')
+
+ # Use singleton instance of the FRR render class
+ if hasattr(conf, 'frrender_cls'):
+ frrender = getattr(conf, 'frrender_cls')
+ dict.update({'frrender_cls' : frrender})
+ frrender.generate(dict)
+
+ return dict
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index 92996f2ee..4084425b1 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,13 @@ 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) -> bool:
+ vrf = None
+ if config_dict and 'vrf_context' in config_dict:
+ vrf = config_dict['vrf_context']
+ 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/defaults.py b/python/vyos/defaults.py
index 425990967..9757a34df 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -42,6 +42,7 @@ directories = {
config_status = '/tmp/vyos-config-status'
api_config_state = '/run/http-api-state'
+frr_debug_enable = '/tmp/vyos.frr.debug'
cfg_group = 'vyattacfg'
diff --git a/python/vyos/frr.py b/python/vyos/frr.py
deleted file mode 100644
index 6fb81803f..000000000
--- a/python/vyos/frr.py
+++ /dev/null
@@ -1,551 +0,0 @@
-# Copyright 2020-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/>.
-
-r"""
-A Library for interracting with the FRR daemon suite.
-It supports simple configuration manipulation and loading using the official tools
-supplied with FRR (vtysh and frr-reload)
-
-All configuration management and manipulation is done using strings and regex.
-
-
-Example Usage
-#####
-
-# Reading configuration from frr:
-```
->>> original_config = get_configuration()
->>> repr(original_config)
-'!\nfrr version 7.3.1\nfrr defaults traditional\nhostname debian\n......
-```
-
-
-# Modify a configuration section:
-```
->>> new_bgp_section = 'router bgp 65000\n neighbor 192.0.2.1 remote-as 65000\n'
->>> modified_config = replace_section(original_config, new_bgp_section, replace_re=r'router bgp \d+')
->>> repr(modified_config)
-'............router bgp 65000\n neighbor 192.0.2.1 remote-as 65000\n...........'
-```
-
-Remove a configuration section:
-```
->>> modified_config = remove_section(original_config, r'router ospf')
-```
-
-Test the new configuration:
-```
->>> try:
->>> mark_configuration(modified configuration)
->>> except ConfigurationNotValid as e:
->>> print('resulting configuration is not valid')
->>> sys.exit(1)
-```
-
-Apply the new configuration:
-```
->>> try:
->>> replace_configuration(modified_config)
->>> except CommitError as e:
->>> print('Exception while commiting the supplied configuration')
->>> print(e)
->>> exit(1)
-```
-"""
-
-import tempfile
-import re
-
-from vyos import ConfigError
-from vyos.utils.process import cmd
-from vyos.utils.process import popen
-from vyos.utils.process import STDOUT
-
-import logging
-from logging.handlers import SysLogHandler
-import os
-import sys
-
-LOG = logging.getLogger(__name__)
-DEBUG = False
-
-ch = SysLogHandler(address='/dev/log')
-ch2 = logging.StreamHandler(stream=sys.stdout)
-LOG.addHandler(ch)
-LOG.addHandler(ch2)
-
-_frr_daemons = ['zebra', 'staticd', 'bgpd', 'ospfd', 'ospf6d', 'ripd', 'ripngd',
- 'isisd', 'pimd', 'pim6d', 'ldpd', 'eigrpd', 'babeld', 'bfdd', 'fabricd']
-
-path_vtysh = '/usr/bin/vtysh'
-path_frr_reload = '/usr/lib/frr/frr-reload.py'
-path_config = '/run/frr'
-
-default_add_before = r'(ip prefix-list .*|route-map .*|line vty|end)'
-
-
-class FrrError(Exception):
- pass
-
-
-class ConfigurationNotValid(FrrError):
- """
- The configuratioin supplied to vtysh is not valid
- """
- pass
-
-
-class CommitError(FrrError):
- """
- Commiting the supplied configuration failed to commit by a unknown reason
- see commit error and/or run mark_configuration on the specified configuration
- to se error generated
-
- used by: reload_configuration()
- """
- pass
-
-
-class ConfigSectionNotFound(FrrError):
- """
- Removal of configuration failed because it is not existing in the supplied configuration
- """
- pass
-
-def init_debugging():
- global DEBUG
-
- DEBUG = os.path.exists('/tmp/vyos.frr.debug')
- if DEBUG:
- LOG.setLevel(logging.DEBUG)
-
-def get_configuration(daemon=None, marked=False):
- """ Get current running FRR configuration
- daemon: Collect only configuration for the specified FRR daemon,
- supplying daemon=None retrieves the complete configuration
- marked: Mark the configuration with "end" tags
-
- return: string containing the running configuration from frr
-
- """
- if daemon and daemon not in _frr_daemons:
- raise ValueError(f'The specified daemon type is not supported {repr(daemon)}')
-
- cmd = f"{path_vtysh} -c 'show run'"
- if daemon:
- cmd += f' -d {daemon}'
-
- output, code = popen(cmd, stderr=STDOUT)
- if code:
- raise OSError(code, output)
-
- config = output.replace('\r', '')
- # Remove first header lines from FRR config
- config = config.split("\n", 3)[-1]
- # Mark the configuration with end tags
- if marked:
- config = mark_configuration(config)
-
- return config
-
-
-def mark_configuration(config):
- """ Add end marks and Test the configuration for syntax faults
- If the configuration is valid a marked version of the configuration is returned,
- or else it failes with a ConfigurationNotValid Exception
-
- config: The configuration string to mark/test
- return: The marked configuration from FRR
- """
- output, code = popen(f"{path_vtysh} -m -f -", stderr=STDOUT, input=config)
-
- if code == 2:
- raise ConfigurationNotValid(str(output))
- elif code:
- raise OSError(code, output)
-
- config = output.replace('\r', '')
- return config
-
-
-def reload_configuration(config, daemon=None):
- """ Execute frr-reload with the new configuration
- This will try to reapply the supplied configuration inside FRR.
- The configuration needs to be a complete configuration from the integrated config or
- from a daemon.
-
- config: The configuration to apply
- daemon: Apply the conigutaion to the specified FRR daemon,
- supplying daemon=None applies to the integrated configuration
- return: None
- """
- if daemon and daemon not in _frr_daemons:
- raise ValueError(f'The specified daemon type is not supported {repr(daemon)}')
-
- f = tempfile.NamedTemporaryFile('w')
- f.write(config)
- f.flush()
-
- LOG.debug(f'reload_configuration: Reloading config using temporary file: {f.name}')
- cmd = f'{path_frr_reload} --reload'
- if daemon:
- cmd += f' --daemon {daemon}'
-
- if DEBUG:
- cmd += f' --debug --stdout'
-
- cmd += f' {f.name}'
-
- LOG.debug(f'reload_configuration: Executing command against frr-reload: "{cmd}"')
- output, code = popen(cmd, stderr=STDOUT)
- f.close()
-
- for i, e in enumerate(output.split('\n')):
- LOG.debug(f'frr-reload output: {i:3} {e}')
-
- if code == 1:
- raise ConfigError(output)
- elif code:
- raise OSError(code, output)
-
- return output
-
-
-def save_configuration():
- """ T3217: Save FRR configuration to /run/frr/config/frr.conf """
- return cmd(f'{path_vtysh} -n -w')
-
-
-def execute(command):
- """ Run commands inside vtysh
- command: str containing commands to execute inside a vtysh session
- """
- if not isinstance(command, str):
- raise ValueError(f'command needs to be a string: {repr(command)}')
-
- cmd = f"{path_vtysh} -c '{command}'"
-
- output, code = popen(cmd, stderr=STDOUT)
- if code:
- raise OSError(code, output)
-
- config = output.replace('\r', '')
- return config
-
-
-def configure(lines, daemon=False):
- """ run commands inside config mode vtysh
- lines: list or str conaining commands to execute inside a configure session
- only one command executed on each configure()
- Executing commands inside a subcontext uses the list to describe the context
- ex: ['router bgp 6500', 'neighbor 192.0.2.1 remote-as 65000']
- return: None
- """
- if isinstance(lines, str):
- lines = [lines]
- elif not isinstance(lines, list):
- raise ValueError('lines needs to be string or list of commands')
-
- if daemon and daemon not in _frr_daemons:
- raise ValueError(f'The specified daemon type is not supported {repr(daemon)}')
-
- cmd = f'{path_vtysh}'
- if daemon:
- cmd += f' -d {daemon}'
-
- cmd += " -c 'configure terminal'"
- for x in lines:
- cmd += f" -c '{x}'"
-
- output, code = popen(cmd, stderr=STDOUT)
- if code == 1:
- raise ConfigurationNotValid(f'Configuration FRR failed: {repr(output)}')
- elif code:
- raise OSError(code, output)
-
- config = output.replace('\r', '')
- return config
-
-
-def _replace_section(config, replacement, replace_re, before_re):
- r"""Replace a section of FRR config
- config: full original configuration
- replacement: replacement configuration section
- replace_re: The regex to replace
- example: ^router bgp \d+$.?*^!$
- this will replace everything between ^router bgp X$ and ^!$
- before_re: When replace_re is not existant, the config will be added before this tag
- example: ^line vty$
-
- return: modified configuration as a text file
- """
- # DEPRECATED, this is replaced by a new implementation
- # Check if block is configured, remove the existing instance else add a new one
- if re.findall(replace_re, config, flags=re.MULTILINE | re.DOTALL):
- # Section is in the configration, replace it
- return re.sub(replace_re, replacement, config, count=1,
- flags=re.MULTILINE | re.DOTALL)
- if before_re:
- if not re.findall(before_re, config, flags=re.MULTILINE | re.DOTALL):
- raise ConfigSectionNotFound(f"Config section {before_re} not found in config")
-
- # If no section is in the configuration, add it before the line vty line
- return re.sub(before_re, rf'{replacement}\n\g<1>', config, count=1,
- flags=re.MULTILINE | re.DOTALL)
-
- raise ConfigSectionNotFound(f"Config section {replacement} not found in config")
-
-
-def replace_section(config, replacement, from_re, to_re=r'!', before_re=r'line vty'):
- r"""Replace a section of FRR config
- config: full original configuration
- replacement: replacement configuration section
- from_re: Regex for the start of section matching
- example: 'router bgp \d+'
- to_re: Regex for stop of section matching
- default: '!'
- example: '!' or 'end'
- before_re: When from_re/to_re does not return a match, the config will
- be added before this tag
- default: ^line vty$
-
- startline and endline tags will be automatically added to the resulting from_re/to_re and before_re regex'es
- """
- # DEPRECATED, this is replaced by a new implementation
- return _replace_section(config, replacement, replace_re=rf'^{from_re}$.*?^{to_re}$', before_re=rf'^({before_re})$')
-
-
-def remove_section(config, from_re, to_re='!'):
- # DEPRECATED, this is replaced by a new implementation
- return _replace_section(config, '', replace_re=rf'^{from_re}$.*?^{to_re}$', before_re=None)
-
-
-def _find_first_block(config, start_pattern, stop_pattern, start_at=0):
- '''Find start and stop line numbers for a config block
- config: (list) A list conaining the configuration that is searched
- start_pattern: (raw-str) The pattern searched for a a start of block tag
- stop_pattern: (raw-str) The pattern searched for to signify the end of the block
- start_at: (int) The index to start searching at in the <config>
-
- Returns:
- None: No complete block could be found
- set(int, int): A complete block found between the line numbers returned in the set
-
- The object <config> is searched from the start for the regex <start_pattern> until the first match is found.
- On a successful match it continues the search for the regex <stop_pattern> until it is found.
- After a successful run a set is returned containing the start and stop line numbers.
- '''
- LOG.debug(f'_find_first_block: find start={repr(start_pattern)} stop={repr(stop_pattern)} start_at={start_at}')
- _start = None
- for i, element in enumerate(config[start_at:], start=start_at):
- # LOG.debug(f'_find_first_block: running line {i:3} "{element}"')
- if not _start:
- if not re.match(start_pattern, element):
- LOG.debug(f'_find_first_block: no match {i:3} "{element}"')
- continue
- _start = i
- LOG.debug(f'_find_first_block: Found start {i:3} "{element}"')
- continue
-
- if not re.match(stop_pattern, element):
- LOG.debug(f'_find_first_block: no match {i:3} "{element}"')
- continue
-
- LOG.debug(f'_find_first_block: Found stop {i:3} "{element}"')
- return (_start, i)
-
- LOG.debug('_find_first_block: exit start={repr(start_pattern)} stop={repr(stop_pattern)} start_at={start_at}')
- return None
-
-
-def _find_first_element(config, pattern, start_at=0):
- '''Find the first element that matches the current pattern in config
- config: (list) A list containing the configuration that is searched
- start_pattern: (raw-str) The pattern searched for
- start_at: (int) The index to start searching at in the <config>
-
- return: Line index of the line containing the searched pattern
-
- TODO: for now it returns -1 on a no-match because 0 also returns as False
- TODO: that means that we can not use False matching to tell if its
- '''
- LOG.debug(f'_find_first_element: find start="{pattern}" start_at={start_at}')
- for i, element in enumerate(config[start_at:], start=0):
- if re.match(pattern + '$', element):
- LOG.debug(f'_find_first_element: Found stop {i:3} "{element}"')
- return i
- LOG.debug(f'_find_first_element: no match {i:3} "{element}"')
- LOG.debug(f'_find_first_element: Did not find any match, exiting')
- return -1
-
-
-def _find_elements(config, pattern, start_at=0):
- '''Find all instances of pattern and return a list containing all element indexes
- config: (list) A list containing the configuration that is searched
- start_pattern: (raw-str) The pattern searched for
- start_at: (int) The index to start searching at in the <config>
-
- return: A list of line indexes containing the searched pattern
- TODO: refactor this to return a generator instead
- '''
- return [i for i, element in enumerate(config[start_at:], start=0) if re.match(pattern + '$', element)]
-
-
-class FRRConfig:
- '''Main FRR Configuration manipulation object
- Using this object the user could load, manipulate and commit the configuration to FRR
- '''
- def __init__(self, config=[]):
- self.imported_config = ''
-
- if isinstance(config, list):
- self.config = config.copy()
- self.original_config = config.copy()
- elif isinstance(config, str):
- self.config = config.split('\n')
- self.original_config = self.config.copy()
- else:
- raise ValueError(
- 'The config element needs to be a string or list type object')
-
- if config:
- LOG.debug(f'__init__: frr library initiated with initial config')
- for i, e in enumerate(self.config):
- LOG.debug(f'__init__: initial {i:3} {e}')
-
- def load_configuration(self, daemon=None):
- '''Load the running configuration from FRR into the config object
- daemon: str with name of the FRR Daemon to load configuration from or
- None to load the consolidated config
-
- Using this overwrites the current loaded config objects and replaces the original loaded config
- '''
- init_debugging()
-
- self.imported_config = get_configuration(daemon=daemon)
- if daemon:
- LOG.debug(f'load_configuration: Configuration loaded from FRR daemon {daemon}')
- else:
- LOG.debug(f'load_configuration: Configuration loaded from FRR integrated config')
-
- self.original_config = self.imported_config.split('\n')
- self.config = self.original_config.copy()
-
- for i, e in enumerate(self.imported_config.split('\n')):
- LOG.debug(f'load_configuration: loaded {i:3} {e}')
- return
-
- def test_configuration(self):
- '''Test the current configuration against FRR
- This will exception if FRR failes to load the current configuration object
- '''
- LOG.debug('test_configation: Testing configuration')
- mark_configuration('\n'.join(self.config))
-
- def commit_configuration(self, daemon=None):
- '''
- Commit the current configuration to FRR daemon: str with name of the
- FRR daemon to commit to or None to use the consolidated config.
-
- Configuration is automatically saved after apply
- '''
- LOG.debug('commit_configuration: Commiting configuration')
- for i, e in enumerate(self.config):
- LOG.debug(f'commit_configuration: new_config {i:3} {e}')
-
- # https://github.com/FRRouting/frr/issues/10132
- # https://github.com/FRRouting/frr/issues/10133
- count = 0
- count_max = 5
- emsg = ''
- while count < count_max:
- count += 1
- try:
- reload_configuration('\n'.join(self.config), daemon=daemon)
- break
- except ConfigError as e:
- emsg = str(e)
- except:
- # we just need to re-try the commit of the configuration
- # for the listed FRR issues above
- pass
- if count >= count_max:
- if emsg:
- raise ConfigError(emsg)
- raise ConfigurationNotValid(f'Config commit retry counter ({count_max}) exceeded for {daemon} daemon!')
-
- # Save configuration to /run/frr/config/frr.conf
- save_configuration()
-
-
- def modify_section(self, start_pattern, replacement='!', stop_pattern=r'\S+', remove_stop_mark=False, count=0):
- if isinstance(replacement, str):
- replacement = replacement.split('\n')
- elif not isinstance(replacement, list):
- return ValueError("The replacement element needs to be a string or list type object")
- LOG.debug(f'modify_section: starting search for {repr(start_pattern)} until {repr(stop_pattern)}')
-
- _count = 0
- _next_start = 0
- while True:
- if count and count <= _count:
- # Break out of the loop after specified amount of matches
- LOG.debug(f'modify_section: reached limit ({_count}), exiting loop at line {_next_start}')
- break
- # While searching, always assume that the user wants to search for the exact pattern he entered
- # To be more specific the user needs a override, eg. a "pattern.*"
- _w = _find_first_block(
- self.config, start_pattern+'$', stop_pattern, start_at=_next_start)
- if not _w:
- # Reached the end, no more elements to remove
- LOG.debug(f'modify_section: No more config sections found, exiting')
- break
- start_element, end_element = _w
- LOG.debug(f'modify_section: found match between {start_element} and {end_element}')
- for i, e in enumerate(self.config[start_element:end_element+1 if remove_stop_mark else end_element],
- start=start_element):
- LOG.debug(f'modify_section: remove {i:3} {e}')
- del self.config[start_element:end_element +
- 1 if remove_stop_mark else end_element]
- if replacement:
- # Append the replacement config at the current position
- for i, e in enumerate(replacement, start=start_element):
- LOG.debug(f'modify_section: add {i:3} {e}')
- self.config[start_element:start_element] = replacement
- _count += 1
- _next_start = start_element + len(replacement)
-
- return _count
-
- def add_before(self, before_pattern, addition):
- '''Add config block before this element in the configuration'''
- if isinstance(addition, str):
- addition = addition.split('\n')
- elif not isinstance(addition, list):
- return ValueError("The replacement element needs to be a string or list type object")
-
- start = _find_first_element(self.config, before_pattern)
- if start < 0:
- return False
- for i, e in enumerate(addition, start=start):
- LOG.debug(f'add_before: add {i:3} {e}')
- self.config[start:start] = addition
- return True
-
- def __str__(self):
- return '\n'.join(self.config)
-
- def __repr__(self):
- return f'frr({repr(str(self))})'
diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py
new file mode 100644
index 000000000..0c9dde315
--- /dev/null
+++ b/python/vyos/frrender.py
@@ -0,0 +1,181 @@
+# 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 time import sleep
+
+from vyos.defaults import frr_debug_enable
+from vyos.utils.file import write_file
+from vyos.utils.process import cmd
+from vyos.utils.process import rc_cmd
+from vyos.template import render_to_string
+from vyos import ConfigError
+
+DEBUG_ON = os.path.exists(frr_debug_enable)
+
+def debug(message):
+ if not DEBUG_ON:
+ return
+ print(message)
+
+frr_protocols = ['babel', 'bfd', 'bgp', 'eigrp', 'isis', 'mpls', 'nhrp',
+ 'openfabric', 'ospf', 'ospfv3', 'pim', 'pim6', 'rip',
+ 'ripng', 'rpki', 'segment_routing', 'static']
+
+babel_daemon = 'babeld'
+bfd_daemon = 'bfdd'
+bgp_daemon = 'bgpd'
+isis_daemon = 'isisd'
+ldpd_daemon = 'ldpd'
+mgmt_daemon = 'mgmtd'
+openfabric_daemon = 'fabricd'
+ospf_daemon = 'ospfd'
+ospf6_daemon = 'ospf6d'
+pim_daemon = 'pimd'
+pim6_daemon = 'pim6d'
+rip_daemon = 'ripd'
+ripng_daemon = 'ripngd'
+zebra_daemon = 'zebra'
+
+class FRRender:
+ def __init__(self):
+ self._frr_conf = '/run/frr/config/vyos.frr.conf'
+
+ def generate(self, config):
+ if not isinstance(config, dict):
+ tmp = type(config)
+ raise ValueError(f'Config must be of type "dict" and not "{tmp}"!')
+
+ 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'
+ if 'ip' in config_dict and 'deleted' not in config_dict['ip']:
+ output += render_to_string('frr/zebra.route-map.frr.j2', config_dict['ip'])
+ output += '\n'
+ if 'ipv6' in config_dict and 'deleted' not in config_dict['ipv6']:
+ output += render_to_string('frr/zebra.route-map.frr.j2', config_dict['ipv6'])
+ 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'])
+ 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'])
+
+ # remove any accidently added empty newline to not confuse FRR
+ output = os.linesep.join([s for s in output.splitlines() if s])
+
+ if '!!' in output:
+ raise ConfigError('FRR configuration contains "!!" which is not allowed')
+
+ debug(output)
+ debug('======< RENDERING CONFIG COMPLETE >======')
+ write_file(self._frr_conf, output)
+
+ def apply(self, count_max=5):
+ count = 0
+ emsg = ''
+ while count < count_max:
+ count += 1
+ debug(f'FRR: Reloading configuration - tries: {count} | Python class ID: {id(self)}')
+ 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:
+ sleep(2)
+ continue
+ debug(emsg)
+ debug('======< DONE APPLYING CONFIG >======')
+ break
+
+ if count >= count_max:
+ raise ConfigError(emsg)
+
+ # T3217: Save FRR configuration to /run/frr/config/frr.conf
+ return cmd('/usr/bin/vtysh -n --writeconfig')
diff --git a/python/vyos/qos/base.py b/python/vyos/qos/base.py
index 12d940e3c..66df5d107 100644
--- a/python/vyos/qos/base.py
+++ b/python/vyos/qos/base.py
@@ -164,11 +164,11 @@ class QoSBase:
default_tc += f' red'
qparams = self._calc_random_detect_queue_params(
- avg_pkt=dict_search('average_packet', config),
- max_thr=dict_search('maximum_threshold', config),
+ avg_pkt=dict_search('average_packet', config) or 1024,
+ max_thr=dict_search('maximum_threshold', config) or 18,
limit=dict_search('queue_limit', config),
min_thr=dict_search('minimum_threshold', config),
- mark_probability=dict_search('mark_probability', config)
+ mark_probability=dict_search('mark_probability', config) or 10
)
default_tc += f' limit {qparams["limit"]} avpkt {qparams["avg_pkt"]}'
@@ -245,8 +245,6 @@ class QoSBase:
prio = cls_config['priority']
filter_cmd_base += f' prio {prio}'
- filter_cmd_base += ' protocol all'
-
if 'match' in cls_config:
has_filter = False
has_action_policy = any(tmp in ['exceed', 'bandwidth', 'burst'] for tmp in cls_config)
@@ -254,13 +252,17 @@ class QoSBase:
for index, (match, match_config) in enumerate(cls_config['match'].items(), start=1):
filter_cmd = filter_cmd_base
if not has_filter:
- for key in ['mark', 'vif', 'ip', 'ipv6', 'interface']:
+ for key in ['mark', 'vif', 'ip', 'ipv6', 'interface', 'ether']:
if key in match_config:
has_filter = True
break
+ tmp = dict_search(f'ether.protocol', match_config) or 'all'
+ filter_cmd += f' protocol {tmp}'
+
if self.qostype in ['shaper', 'shaper_hfsc'] and 'prio ' not in filter_cmd:
filter_cmd += f' prio {index}'
+
if 'mark' in match_config:
mark = match_config['mark']
filter_cmd += f' handle {mark} fw'
@@ -273,7 +275,7 @@ class QoSBase:
iif = Interface(iif_name).get_ifindex()
filter_cmd += f' basic match "meta(rt_iif eq {iif})"'
- for af in ['ip', 'ipv6']:
+ for af in ['ip', 'ipv6', 'ether']:
tc_af = af
if af == 'ipv6':
tc_af = 'ip6'
@@ -281,67 +283,77 @@ class QoSBase:
if af in match_config:
filter_cmd += ' u32'
- tmp = dict_search(f'{af}.source.address', match_config)
- if tmp: filter_cmd += f' match {tc_af} src {tmp}'
-
- tmp = dict_search(f'{af}.source.port', match_config)
- if tmp: filter_cmd += f' match {tc_af} sport {tmp} 0xffff'
-
- tmp = dict_search(f'{af}.destination.address', match_config)
- if tmp: filter_cmd += f' match {tc_af} dst {tmp}'
-
- tmp = dict_search(f'{af}.destination.port', match_config)
- if tmp: filter_cmd += f' match {tc_af} dport {tmp} 0xffff'
-
- tmp = dict_search(f'{af}.protocol', match_config)
- if tmp:
- tmp = get_protocol_by_name(tmp)
- filter_cmd += f' match {tc_af} protocol {tmp} 0xff'
-
- tmp = dict_search(f'{af}.dscp', match_config)
- if tmp:
- tmp = self._get_dsfield(tmp)
- if af == 'ip':
- filter_cmd += f' match {tc_af} dsfield {tmp} 0xff'
- elif af == 'ipv6':
- filter_cmd += f' match u16 {tmp} 0x0ff0 at 0'
-
- # Will match against total length of an IPv4 packet and
- # payload length of an IPv6 packet.
- #
- # IPv4 : match u16 0x0000 ~MAXLEN at 2
- # IPv6 : match u16 0x0000 ~MAXLEN at 4
- tmp = dict_search(f'{af}.max_length', match_config)
- if tmp:
- # We need the 16 bit two's complement of the maximum
- # packet length
- tmp = hex(0xffff & ~int(tmp))
-
- if af == 'ip':
- filter_cmd += f' match u16 0x0000 {tmp} at 2'
- elif af == 'ipv6':
- filter_cmd += f' match u16 0x0000 {tmp} at 4'
-
- # We match against specific TCP flags - we assume the IPv4
- # header length is 20 bytes and assume the IPv6 packet is
- # not using extension headers (hence a ip header length of 40 bytes)
- # TCP Flags are set on byte 13 of the TCP header.
- # IPv4 : match u8 X X at 33
- # IPv6 : match u8 X X at 53
- # with X = 0x02 for SYN and X = 0x10 for ACK
- tmp = dict_search(f'{af}.tcp', match_config)
- if tmp:
- mask = 0
- if 'ack' in tmp:
- mask |= 0x10
- if 'syn' in tmp:
- mask |= 0x02
- mask = hex(mask)
-
- if af == 'ip':
- filter_cmd += f' match u8 {mask} {mask} at 33'
- elif af == 'ipv6':
- filter_cmd += f' match u8 {mask} {mask} at 53'
+ if af == 'ether':
+ src = dict_search(f'{af}.source', match_config)
+ if src: filter_cmd += f' match {tc_af} src {src}'
+
+ dst = dict_search(f'{af}.destination', match_config)
+ if dst: filter_cmd += f' match {tc_af} dst {dst}'
+
+ if not src and not dst:
+ filter_cmd += f' match u32 0 0'
+ else:
+ tmp = dict_search(f'{af}.source.address', match_config)
+ if tmp: filter_cmd += f' match {tc_af} src {tmp}'
+
+ tmp = dict_search(f'{af}.source.port', match_config)
+ if tmp: filter_cmd += f' match {tc_af} sport {tmp} 0xffff'
+
+ tmp = dict_search(f'{af}.destination.address', match_config)
+ if tmp: filter_cmd += f' match {tc_af} dst {tmp}'
+
+ tmp = dict_search(f'{af}.destination.port', match_config)
+ if tmp: filter_cmd += f' match {tc_af} dport {tmp} 0xffff'
+ ###
+ tmp = dict_search(f'{af}.protocol', match_config)
+ if tmp:
+ tmp = get_protocol_by_name(tmp)
+ filter_cmd += f' match {tc_af} protocol {tmp} 0xff'
+
+ tmp = dict_search(f'{af}.dscp', match_config)
+ if tmp:
+ tmp = self._get_dsfield(tmp)
+ if af == 'ip':
+ filter_cmd += f' match {tc_af} dsfield {tmp} 0xff'
+ elif af == 'ipv6':
+ filter_cmd += f' match u16 {tmp} 0x0ff0 at 0'
+
+ # Will match against total length of an IPv4 packet and
+ # payload length of an IPv6 packet.
+ #
+ # IPv4 : match u16 0x0000 ~MAXLEN at 2
+ # IPv6 : match u16 0x0000 ~MAXLEN at 4
+ tmp = dict_search(f'{af}.max_length', match_config)
+ if tmp:
+ # We need the 16 bit two's complement of the maximum
+ # packet length
+ tmp = hex(0xffff & ~int(tmp))
+
+ if af == 'ip':
+ filter_cmd += f' match u16 0x0000 {tmp} at 2'
+ elif af == 'ipv6':
+ filter_cmd += f' match u16 0x0000 {tmp} at 4'
+
+ # We match against specific TCP flags - we assume the IPv4
+ # header length is 20 bytes and assume the IPv6 packet is
+ # not using extension headers (hence a ip header length of 40 bytes)
+ # TCP Flags are set on byte 13 of the TCP header.
+ # IPv4 : match u8 X X at 33
+ # IPv6 : match u8 X X at 53
+ # with X = 0x02 for SYN and X = 0x10 for ACK
+ tmp = dict_search(f'{af}.tcp', match_config)
+ if tmp:
+ mask = 0
+ if 'ack' in tmp:
+ mask |= 0x10
+ if 'syn' in tmp:
+ mask |= 0x02
+ mask = hex(mask)
+
+ if af == 'ip':
+ filter_cmd += f' match u8 {mask} {mask} at 33'
+ elif af == 'ipv6':
+ filter_cmd += f' match u8 {mask} {mask} at 53'
if index != max_index or not has_action_policy:
# avoid duplicate last match rule
diff --git a/python/vyos/qos/priority.py b/python/vyos/qos/priority.py
index 7f0a67032..66d27a639 100644
--- a/python/vyos/qos/priority.py
+++ b/python/vyos/qos/priority.py
@@ -20,17 +20,18 @@ class Priority(QoSBase):
# https://man7.org/linux/man-pages/man8/tc-prio.8.html
def update(self, config, direction):
- if 'class' in config:
- class_id_max = self._get_class_max_id(config)
- bands = int(class_id_max) +1
+ class_id_max = self._get_class_max_id(config)
+ class_id_max = class_id_max if class_id_max else 1
+ bands = int(class_id_max) + 1
- tmp = f'tc qdisc add dev {self._interface} root handle {self._parent:x}: prio bands {bands} priomap ' \
- f'{class_id_max} {class_id_max} {class_id_max} {class_id_max} ' \
- f'{class_id_max} {class_id_max} {class_id_max} {class_id_max} ' \
- f'{class_id_max} {class_id_max} {class_id_max} {class_id_max} ' \
- f'{class_id_max} {class_id_max} {class_id_max} {class_id_max} '
- self._cmd(tmp)
+ tmp = f'tc qdisc add dev {self._interface} root handle {self._parent:x}: prio bands {bands} priomap ' \
+ f'{class_id_max} {class_id_max} {class_id_max} {class_id_max} ' \
+ f'{class_id_max} {class_id_max} {class_id_max} {class_id_max} ' \
+ f'{class_id_max} {class_id_max} {class_id_max} {class_id_max} ' \
+ f'{class_id_max} {class_id_max} {class_id_max} {class_id_max} '
+ self._cmd(tmp)
+ if 'class' in config:
for cls in config['class']:
cls = int(cls)
tmp = f'tc qdisc add dev {self._interface} parent {self._parent:x}:{cls:x} pfifo'
diff --git a/python/vyos/utils/process.py b/python/vyos/utils/process.py
index ce880f4a4..d8aabb822 100644
--- a/python/vyos/utils/process.py
+++ b/python/vyos/utils/process.py
@@ -128,7 +128,7 @@ def run(command, flag='', shell=None, input=None, timeout=None, env=None,
def cmd(command, flag='', shell=None, input=None, timeout=None, env=None,
stdout=PIPE, stderr=PIPE, decode='utf-8', raising=None, message='',
- expect=[0]):
+ expect=[0], auth=''):
"""
A wrapper around popen, which returns the stdout and
will raise the error code of a command
@@ -139,7 +139,7 @@ def cmd(command, flag='', shell=None, input=None, timeout=None, env=None,
expect: a list of error codes to consider as normal
"""
decoded, code = popen(
- command, flag,
+ f'{auth} {command}'.strip(), flag,
stdout=stdout, stderr=stderr,
input=input, timeout=timeout,
env=env, shell=shell,
diff --git a/smoketest/config-tests/static-route-basic b/smoketest/config-tests/static-route-basic
new file mode 100644
index 000000000..d2d33d043
--- /dev/null
+++ b/smoketest/config-tests/static-route-basic
@@ -0,0 +1,37 @@
+set interfaces ethernet eth0 duplex 'auto'
+set interfaces ethernet eth0 speed 'auto'
+set interfaces ethernet eth0 vif 203 address '172.18.203.10/24'
+set interfaces ethernet eth1 duplex 'auto'
+set interfaces ethernet eth1 speed 'auto'
+set protocols static mroute 224.1.0.0/24 interface eth0.203 distance '10'
+set protocols static mroute 224.2.0.0/24 next-hop 172.18.203.254 distance '20'
+set protocols static route 10.0.0.0/8 blackhole distance '200'
+set protocols static route 10.0.0.0/8 blackhole tag '333'
+set protocols static route 10.0.0.0/8 next-hop 192.0.2.140 bfd multi-hop source-address '192.0.2.10'
+set protocols static route 10.0.0.0/8 next-hop 192.0.2.140 bfd profile 'vyos-test'
+set protocols static route 10.0.0.0/8 next-hop 192.0.2.140 distance '123'
+set protocols static route 10.0.0.0/8 next-hop 192.0.2.140 interface 'eth0'
+set protocols static route 172.16.0.0/16 next-hop 172.18.203.254 bfd multi-hop source-address '172.18.203.254'
+set protocols static route 172.16.0.0/16 next-hop 172.18.203.254 bfd profile 'foo'
+set protocols static route6 2001:db8:1::/48 next-hop fe80::1 bfd multi-hop source-address 'fe80::1'
+set protocols static route6 2001:db8:1::/48 next-hop fe80::1 bfd profile 'bar'
+set protocols static route6 2001:db8:1::/48 next-hop fe80::1 interface 'eth0.203'
+set protocols static route6 2001:db8:2::/48 next-hop fe80::1 bfd multi-hop source-address 'fe80::1'
+set protocols static route6 2001:db8:2::/48 next-hop fe80::1 bfd profile 'bar'
+set protocols static route6 2001:db8:2::/48 next-hop fe80::1 interface 'eth0.203'
+set protocols static route6 2001:db8:3::/48 next-hop fe80::1 bfd
+set protocols static route6 2001:db8:3::/48 next-hop fe80::1 interface 'eth0.203'
+set service lldp interface all
+set service ntp allow-client address '0.0.0.0/0'
+set service ntp allow-client address '::/0'
+set service ntp server 172.16.100.10
+set service ntp server 172.16.100.20
+set service ntp server 172.16.110.30
+set system config-management commit-revisions '100'
+set system console device ttyS0 speed '115200'
+set system host-name 'vyos'
+set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0'
+set system login user vyos authentication plaintext-password ''
+set system syslog global facility all level 'info'
+set system syslog global facility local7 level 'debug'
+set system time-zone 'Asia/Macau'
diff --git a/smoketest/configs/static-route-basic b/smoketest/configs/static-route-basic
new file mode 100644
index 000000000..648e19676
--- /dev/null
+++ b/smoketest/configs/static-route-basic
@@ -0,0 +1,148 @@
+interfaces {
+ ethernet eth0 {
+ duplex "auto"
+ speed "auto"
+ vif 203 {
+ address "172.18.203.10/24"
+ }
+ }
+ ethernet eth1 {
+ duplex "auto"
+ speed "auto"
+ }
+}
+protocols {
+ static {
+ multicast {
+ interface-route 224.1.0.0/24 {
+ next-hop-interface eth0.203 {
+ distance "10"
+ }
+ }
+ route 224.2.0.0/24 {
+ next-hop 172.18.203.254 {
+ distance "20"
+ }
+ }
+ }
+ route 10.0.0.0/8 {
+ blackhole {
+ distance "200"
+ tag "333"
+ }
+ next-hop 192.0.2.140 {
+ bfd {
+ multi-hop {
+ source 192.0.2.10 {
+ profile "vyos-test"
+ }
+ }
+ }
+ distance "123"
+ interface "eth0"
+ }
+ }
+ route 172.16.0.0/16 {
+ next-hop 172.18.203.254 {
+ bfd {
+ multi-hop {
+ source 172.18.203.254 {
+ profile "foo"
+ }
+ }
+ }
+ }
+ }
+ route6 2001:db8:1::/48 {
+ next-hop fe80::1 {
+ bfd {
+ multi-hop {
+ source fe80::1 {
+ profile "bar"
+ }
+ }
+ }
+ interface eth0.203
+ }
+ }
+ route6 2001:db8:2::/48 {
+ next-hop fe80::1 {
+ bfd {
+ multi-hop {
+ source fe80::1 {
+ profile "bar"
+ }
+ }
+ }
+ interface eth0.203
+ }
+ }
+ route6 2001:db8:3::/48 {
+ next-hop fe80::1 {
+ bfd {
+ }
+ interface eth0.203
+ }
+ }
+ }
+}
+service {
+ lldp {
+ interface all {
+ }
+ }
+ ntp {
+ allow-client {
+ address "0.0.0.0/0"
+ address "::/0"
+ }
+ server 172.16.100.10 {
+ }
+ server 172.16.100.20 {
+ }
+ server 172.16.110.30 {
+ }
+ }
+}
+system {
+ config-management {
+ commit-revisions 100
+ }
+ console {
+ device ttyS0 {
+ speed 115200
+ }
+ }
+ host-name vyos
+ login {
+ user vyos {
+ authentication {
+ encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0
+ plaintext-password ""
+ }
+ }
+ }
+ ntp {
+ server 0.pool.ntp.org {
+ }
+ server 1.pool.ntp.org {
+ }
+ server 2.pool.ntp.org {
+ }
+ }
+ syslog {
+ global {
+ facility all {
+ level info
+ }
+ facility local7 {
+ level debug
+ }
+ }
+ }
+ time-zone "Asia/Macau"
+}
+
+// Warning: Do not remove the following line.
+// vyos-config-version: "bgp@5:broadcast-relay@1:cluster@2:config-management@1:conntrack@5:conntrack-sync@2:container@2:dhcp-relay@2:dhcp-server@8:dhcpv6-server@1:dns-dynamic@4:dns-forwarding@4:firewall@15:flow-accounting@1:https@6:ids@1:interfaces@32:ipoe-server@3:ipsec@13:isis@3:l2tp@9:lldp@2:mdns@1:monitoring@1:nat@8:nat66@3:ntp@3:openconnect@3:ospf@2:pim@1:policy@8:pppoe-server@10:pptp@5:qos@2:quagga@11:reverse-proxy@1:rip@1:rpki@2:salt@1:snmp@3:ssh@2:sstp@6:system@27:vrf@3:vrrp@4:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2"
+// Release version: 1.4.0
diff --git a/smoketest/scripts/cli/base_vyostest_shim.py b/smoketest/scripts/cli/base_vyostest_shim.py
index a383e596c..d95071d1a 100644
--- a/smoketest/scripts/cli/base_vyostest_shim.py
+++ b/smoketest/scripts/cli/base_vyostest_shim.py
@@ -18,6 +18,7 @@ import paramiko
import pprint
from time import sleep
+from time import time
from typing import Type
from vyos.configsession import ConfigSession
@@ -43,7 +44,7 @@ class VyOSUnitTestSHIM:
# trigger the certain failure condition.
# Use "self.debug = True" in derived classes setUp() method
debug = False
-
+ commit_guard = time()
@classmethod
def setUpClass(cls):
cls._session = ConfigSession(os.getpid())
@@ -87,6 +88,8 @@ class VyOSUnitTestSHIM:
# during a commit there is a process opening commit_lock, and run() returns 0
while run(f'sudo lsof -nP {commit_lock}') == 0:
sleep(0.250)
+ # reset getFRRconfig() guard timer
+ self.commit_guard = time()
def op_mode(self, path : list) -> None:
"""
@@ -101,14 +104,29 @@ class VyOSUnitTestSHIM:
pprint.pprint(out)
return out
- def getFRRconfig(self, string=None, end='$', endsection='^!', daemon=''):
+ def getFRRconfig(self, string=None, end='$', endsection='^!', daemon='', guard_time=10, empty_retry=0):
""" Retrieve current "running configuration" from FRR """
+ # Sometimes FRR needs some time after reloading the configuration to
+ # appear in vtysh. This is a workaround addiung a 10 second guard timer
+ # between the last cli_commit() and the first read of FRR config via vtysh
+ while (time() - self.commit_guard) < guard_time:
+ sleep(0.250) # wait 250 milliseconds
command = f'vtysh -c "show run {daemon} no-header"'
if string: command += f' | sed -n "/^{string}{end}/,/{endsection}/p"'
out = cmd(command)
if self.debug:
print(f'\n\ncommand "{command}" returned:\n')
pprint.pprint(out)
+ if empty_retry:
+ retry_count = 0
+ while not out and retry_count < empty_retry:
+ if self.debug and retry_count % 10 == 0:
+ print(f"Attempt {retry_count}: FRR config is still empty. Retrying...")
+ retry_count += 1
+ sleep(1)
+ out = cmd(command)
+ if not out:
+ print(f'FRR configuration still empty after {empty_retry} retires!')
return out
@staticmethod
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 2d18f0495..6420afa38 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -765,6 +765,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['type filter hook output priority filter; policy accept;'],
['ct state invalid', 'udp sport 67', 'udp dport 68', 'accept'],
['ct state invalid', 'ether type arp', 'accept'],
+ ['ct state invalid', 'ether type 0x8864', 'accept'],
['chain VYOS_PREROUTING_filter'],
['type filter hook prerouting priority filter; policy accept;'],
['ip6 daddr @A6_AGV6', 'notrack'],
diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py
index f436424b8..735e4f3c5 100755
--- a/smoketest/scripts/cli/test_interfaces_bonding.py
+++ b/smoketest/scripts/cli/test_interfaces_bonding.py
@@ -24,6 +24,7 @@ from vyos.ifconfig.interface import Interface
from vyos.configsession import ConfigSessionError
from vyos.utils.network import get_interface_config
from vyos.utils.file import read_file
+from vyos.frrender import mgmt_daemon
class BondingInterfaceTest(BasicInterfaceTest.TestCase):
@classmethod
@@ -286,7 +287,7 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase):
id = '5'
for interface in self._interfaces:
- frrconfig = self.getFRRconfig(f'interface {interface}', daemon='zebra')
+ frrconfig = self.getFRRconfig(f'interface {interface}', daemon=mgmt_daemon)
self.assertIn(f' evpn mh es-id {id}', frrconfig)
self.assertIn(f' evpn mh es-df-pref {id}', frrconfig)
@@ -303,7 +304,7 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase):
id = '5'
for interface in self._interfaces:
- frrconfig = self.getFRRconfig(f'interface {interface}', daemon='zebra')
+ frrconfig = self.getFRRconfig(f'interface {interface}', daemon=mgmt_daemon)
self.assertIn(f' evpn mh es-sys-mac 00:12:34:56:78:0{id}', frrconfig)
self.assertIn(f' evpn mh uplink', frrconfig)
diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py
index 3d12364f7..c02ca613b 100755
--- a/smoketest/scripts/cli/test_interfaces_ethernet.py
+++ b/smoketest/scripts/cli/test_interfaces_ethernet.py
@@ -27,6 +27,7 @@ from netifaces import ifaddresses
from base_interfaces_test import BasicInterfaceTest
from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Section
+from vyos.frrender import mgmt_daemon
from vyos.utils.process import cmd
from vyos.utils.process import popen
from vyos.utils.file import read_file
@@ -219,7 +220,7 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase):
self.cli_commit()
for interface in self._interfaces:
- frrconfig = self.getFRRconfig(f'interface {interface}', daemon='zebra')
+ frrconfig = self.getFRRconfig(f'interface {interface}', daemon=mgmt_daemon)
self.assertIn(f' evpn mh uplink', frrconfig)
if __name__ == '__main__':
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_babel.py b/smoketest/scripts/cli/test_protocols_babel.py
index 606c1efd3..107e262cc 100755
--- a/smoketest/scripts/cli/test_protocols_babel.py
+++ b/smoketest/scripts/cli/test_protocols_babel.py
@@ -19,10 +19,10 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.ifconfig import Section
+from vyos.frrender import babel_daemon
from vyos.utils.process import process_named_running
from vyos.xml_ref import default_value
-PROCESS_NAME = 'babeld'
base_path = ['protocols', 'babel']
class TestProtocolsBABEL(VyOSUnitTestSHIM.TestCase):
@@ -32,7 +32,7 @@ class TestProtocolsBABEL(VyOSUnitTestSHIM.TestCase):
# call base-classes classmethod
super(TestProtocolsBABEL, cls).setUpClass()
# Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same
- cls.daemon_pid = process_named_running(PROCESS_NAME)
+ cls.daemon_pid = process_named_running(babel_daemon)
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
cls.cli_delete(cls, base_path)
@@ -48,7 +48,7 @@ class TestProtocolsBABEL(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# check process health and continuity
- self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME))
+ self.assertEqual(self.daemon_pid, process_named_running(babel_daemon))
def test_babel_interfaces(self):
def_update_interval = default_value(base_path + ['interface', 'eth0', 'update-interval'])
@@ -77,11 +77,11 @@ class TestProtocolsBABEL(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
- frrconfig = self.getFRRconfig('router babel', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon)
for interface in self._interfaces:
self.assertIn(f' network {interface}', frrconfig)
- iface_config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ iface_config = self.getFRRconfig(f'interface {interface}', endsection='^exit', daemon=babel_daemon)
self.assertIn(f' babel channel {channel}', iface_config)
self.assertIn(f' babel enable-timestamps', iface_config)
self.assertIn(f' babel update-interval {def_update_interval}', iface_config)
@@ -105,7 +105,7 @@ class TestProtocolsBABEL(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
- frrconfig = self.getFRRconfig('router babel', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon, empty_retry=5)
for protocol in ipv4_protos:
self.assertIn(f' redistribute ipv4 {protocol}', frrconfig)
for protocol in ipv6_protos:
@@ -125,7 +125,7 @@ class TestProtocolsBABEL(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
- frrconfig = self.getFRRconfig('router babel', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon)
self.assertIn(f' babel diversity', frrconfig)
self.assertIn(f' babel diversity-factor {diversity_factor}', frrconfig)
self.assertIn(f' babel resend-delay {resend_delay}', frrconfig)
@@ -192,7 +192,7 @@ class TestProtocolsBABEL(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
- frrconfig = self.getFRRconfig('router babel', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon)
self.assertIn(f' distribute-list {access_list_in4} in', frrconfig)
self.assertIn(f' distribute-list {access_list_out4} out', frrconfig)
self.assertIn(f' ipv6 distribute-list {access_list_in6} in', frrconfig)
diff --git a/smoketest/scripts/cli/test_protocols_bfd.py b/smoketest/scripts/cli/test_protocols_bfd.py
index 716d0a806..32e39e8f7 100755
--- a/smoketest/scripts/cli/test_protocols_bfd.py
+++ b/smoketest/scripts/cli/test_protocols_bfd.py
@@ -18,10 +18,11 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
+from vyos.frrender import bfd_daemon
from vyos.utils.process import process_named_running
-PROCESS_NAME = 'bfdd'
base_path = ['protocols', 'bfd']
+frr_endsection = '^ exit'
dum_if = 'dum1001'
vrf_name = 'red'
@@ -84,7 +85,7 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase):
super(TestProtocolsBFD, cls).setUpClass()
# Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same
- cls.daemon_pid = process_named_running(PROCESS_NAME)
+ cls.daemon_pid = process_named_running(bfd_daemon)
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
@@ -95,7 +96,7 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# check process health and continuity
- self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME))
+ self.assertEqual(self.daemon_pid, process_named_running(bfd_daemon))
def test_bfd_peer(self):
self.cli_set(['vrf', 'name', vrf_name, 'table', '1000'])
@@ -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=bfd_daemon)
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=bfd_daemon)
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=bfd_daemon)
if 'profile' in peer_config:
self.assertIn(f' profile {peer_config["profile"]}', peerconfig)
diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py
index ea2f561a4..cdf18a051 100755
--- a/smoketest/scripts/cli/test_protocols_bgp.py
+++ b/smoketest/scripts/cli/test_protocols_bgp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2023 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
@@ -25,8 +25,8 @@ from vyos.configsession import ConfigSessionError
from vyos.template import is_ipv6
from vyos.utils.process import process_named_running
from vyos.utils.process import cmd
+from vyos.frrender import bgp_daemon
-PROCESS_NAME = 'bgpd'
ASN = '64512'
base_path = ['protocols', 'bgp']
@@ -178,7 +178,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
super(TestProtocolsBGP, cls).setUpClass()
# Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same
- cls.daemon_pid = process_named_running(PROCESS_NAME)
+ cls.daemon_pid = process_named_running(bgp_daemon)
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
@@ -217,8 +217,11 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_commit()
+ frrconfig = self.getFRRconfig('router bgp', endsection='^exit', daemon=bgp_daemon)
+ self.assertNotIn(f'router bgp', frrconfig)
+
# check process health and continuity
- self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME))
+ self.assertEqual(self.daemon_pid, process_named_running(bgp_daemon))
def create_bgp_instances_for_import_test(self):
table = '1000'
@@ -369,7 +372,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR bgpd configuration
- frrconfig = self.getFRRconfig(f'router bgp {ASN}')
+ frrconfig = self.getFRRconfig(f'router bgp {ASN}', daemon=bgp_daemon)
self.assertIn(f'router bgp {ASN}', frrconfig)
self.assertIn(f' bgp router-id {router_id}', frrconfig)
self.assertIn(f' bgp allow-martian-nexthop', frrconfig)
@@ -395,15 +398,18 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertNotIn(f'bgp ebgp-requires-policy', frrconfig)
self.assertIn(f' no bgp suppress-duplicates', frrconfig)
- afiv4_config = self.getFRRconfig(' address-family ipv4 unicast')
+ afiv4_config = self.getFRRconfig(' address-family ipv4 unicast',
+ endsection='^ exit-address-family', daemon=bgp_daemon)
self.assertIn(f' maximum-paths {max_path_v4}', afiv4_config)
self.assertIn(f' maximum-paths ibgp {max_path_v4ibgp}', afiv4_config)
- afiv4_config = self.getFRRconfig(' address-family ipv4 labeled-unicast')
+ afiv4_config = self.getFRRconfig(' address-family ipv4 labeled-unicast',
+ endsection='^ exit-address-family', daemon=bgp_daemon)
self.assertIn(f' maximum-paths {max_path_v4}', afiv4_config)
self.assertIn(f' maximum-paths ibgp {max_path_v4ibgp}', afiv4_config)
- afiv6_config = self.getFRRconfig(' address-family ipv6 unicast')
+ afiv6_config = self.getFRRconfig(' address-family ipv6 unicast',
+ endsection='^ exit-address-family', daemon=bgp_daemon)
self.assertIn(f' maximum-paths {max_path_v6}', afiv6_config)
self.assertIn(f' maximum-paths ibgp {max_path_v6ibgp}', afiv6_config)
@@ -510,7 +516,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR bgpd configuration
- frrconfig = self.getFRRconfig(f'router bgp {ASN}')
+ frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', daemon=bgp_daemon)
self.assertIn(f'router bgp {ASN}', frrconfig)
for peer, peer_config in neighbor_config.items():
@@ -615,7 +621,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR bgpd configuration
- frrconfig = self.getFRRconfig(f'router bgp {ASN}')
+ frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', daemon=bgp_daemon)
self.assertIn(f'router bgp {ASN}', frrconfig)
for peer, peer_config in peer_group_config.items():
@@ -664,7 +670,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR bgpd configuration
- frrconfig = self.getFRRconfig(f'router bgp {ASN}')
+ frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', daemon=bgp_daemon)
self.assertIn(f'router bgp {ASN}', frrconfig)
self.assertIn(f' address-family ipv4 unicast', frrconfig)
@@ -710,7 +716,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR bgpd configuration
- frrconfig = self.getFRRconfig(f'router bgp {ASN}')
+ frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', daemon=bgp_daemon)
self.assertIn(f'router bgp {ASN}', frrconfig)
self.assertIn(f' address-family ipv6 unicast', frrconfig)
# T2100: By default ebgp-requires-policy is disabled to keep VyOS
@@ -752,7 +758,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR bgpd configuration
- frrconfig = self.getFRRconfig(f'router bgp {ASN}')
+ frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', daemon=bgp_daemon)
self.assertIn(f'router bgp {ASN}', frrconfig)
self.assertIn(f' neighbor {peer_group} peer-group', frrconfig)
self.assertIn(f' neighbor {peer_group} remote-as {ASN}', frrconfig)
@@ -787,7 +793,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR bgpd configuration
- frrconfig = self.getFRRconfig(f'router bgp {ASN}')
+ frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', daemon=bgp_daemon)
self.assertIn(f'router bgp {ASN}', frrconfig)
self.assertIn(f' address-family l2vpn evpn', frrconfig)
self.assertIn(f' advertise-all-vni', frrconfig)
@@ -800,7 +806,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' flooding disable', frrconfig)
self.assertIn(f' mac-vrf soo {soo}', frrconfig)
for vni in vnis:
- vniconfig = self.getFRRconfig(f' vni {vni}')
+ vniconfig = self.getFRRconfig(f' vni {vni}', endsection='^exit-vni')
self.assertIn(f'vni {vni}', vniconfig)
self.assertIn(f' advertise-default-gw', vniconfig)
self.assertIn(f' advertise-svi-ip', vniconfig)
@@ -960,7 +966,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'router bgp {ASN}', frrconfig)
for afi in ['ipv4', 'ipv6']:
- afi_config = self.getFRRconfig(f' address-family {afi} unicast', endsection='exit-address-family', daemon='bgpd')
+ afi_config = self.getFRRconfig(f' address-family {afi} unicast', endsection='exit-address-family', daemon=bgp_daemon)
self.assertIn(f'address-family {afi} unicast', afi_config)
self.assertIn(f' export vpn', afi_config)
self.assertIn(f' import vpn', afi_config)
@@ -1379,7 +1385,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
# let the bgpd process recover
sleep(10)
# update daemon PID - this was a planned daemon restart
- self.daemon_pid = process_named_running(PROCESS_NAME)
+ self.daemon_pid = process_named_running(bgp_daemon)
# set bmp config but not set address
self.cli_set(target_path + ['port', target_port])
diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py
index 769f3dd33..bde32a1ca 100755
--- a/smoketest/scripts/cli/test_protocols_isis.py
+++ b/smoketest/scripts/cli/test_protocols_isis.py
@@ -20,8 +20,8 @@ from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Section
from vyos.utils.process import process_named_running
+from vyos.frrender import isis_daemon
-PROCESS_NAME = 'isisd'
base_path = ['protocols', 'isis']
domain = 'VyOS'
@@ -34,7 +34,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
# call base-classes classmethod
super(TestProtocolsISIS, cls).setUpClass()
# Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same
- cls.daemon_pid = process_named_running(PROCESS_NAME)
+ cls.daemon_pid = process_named_running(isis_daemon)
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
cls.cli_delete(cls, base_path)
@@ -49,7 +49,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# check process health and continuity
- self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME))
+ self.assertEqual(self.daemon_pid, process_named_running(isis_daemon))
def isis_base_config(self):
self.cli_set(base_path + ['net', net])
@@ -88,14 +88,14 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify all changes
- tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd')
+ tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon)
self.assertIn(f' net {net}', tmp)
self.assertIn(f' metric-style {metric_style}', tmp)
self.assertIn(f' log-adjacency-changes', tmp)
self.assertIn(f' redistribute ipv4 connected level-2 route-map {route_map}', tmp)
for interface in self._interfaces:
- tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd')
+ tmp = self.getFRRconfig(f'interface {interface}', daemon=isis_daemon)
self.assertIn(f' ip router isis {domain}', tmp)
self.assertIn(f' ipv6 router isis {domain}', tmp)
@@ -124,11 +124,11 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR isisd configuration
- tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd')
+ tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon)
self.assertIn(f'router isis {domain}', tmp)
self.assertIn(f' net {net}', tmp)
- tmp = self.getFRRconfig(f'router isis {domain} vrf {vrf}', daemon='isisd')
+ tmp = self.getFRRconfig(f'router isis {domain} vrf {vrf}', daemon=isis_daemon)
self.assertIn(f'router isis {domain} vrf {vrf}', tmp)
self.assertIn(f' net {net}', tmp)
self.assertIn(f' advertise-high-metrics', tmp)
@@ -152,7 +152,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify all changes
- tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd')
+ tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon)
self.assertIn(f' net {net}', tmp)
for afi in ['ipv4', 'ipv6']:
@@ -187,13 +187,13 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify all changes
- tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd')
+ tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon)
self.assertIn(f' net {net}', tmp)
self.assertIn(f' domain-password clear {password}', tmp)
self.assertIn(f' area-password clear {password}', tmp)
for interface in self._interfaces:
- tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd')
+ tmp = self.getFRRconfig(f'interface {interface}', daemon=isis_daemon)
self.assertIn(f' isis password clear {password}-{interface}', tmp)
def test_isis_06_spf_delay_bfd(self):
@@ -235,12 +235,12 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify all changes
- tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd')
+ tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon)
self.assertIn(f' net {net}', tmp)
self.assertIn(f' spf-delay-ietf init-delay {init_delay} short-delay {short_delay} long-delay {long_delay} holddown {holddown} time-to-learn {time_to_learn}', tmp)
for interface in self._interfaces:
- tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd')
+ tmp = self.getFRRconfig(f'interface {interface}', daemon=isis_daemon)
self.assertIn(f' ip router isis {domain}', tmp)
self.assertIn(f' ipv6 router isis {domain}', tmp)
self.assertIn(f' isis network {network}', tmp)
@@ -283,7 +283,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify all changes
- tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd')
+ tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon)
self.assertIn(f' net {net}', tmp)
self.assertIn(f' segment-routing on', tmp)
self.assertIn(f' segment-routing global-block {global_block_low} {global_block_high} local-block {local_block_low} {local_block_high}', tmp)
@@ -305,7 +305,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify main ISIS changes
- tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd')
+ tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon)
self.assertIn(f' net {net}', tmp)
self.assertIn(f' mpls ldp-sync', tmp)
self.assertIn(f' mpls ldp-sync holddown {holddown}', tmp)
@@ -318,7 +318,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
for interface in self._interfaces:
# Verify interface changes for holddown
- tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd')
+ tmp = self.getFRRconfig(f'interface {interface}', daemon=isis_daemon)
self.assertIn(f'interface {interface}', tmp)
self.assertIn(f' ip router isis {domain}', tmp)
self.assertIn(f' ipv6 router isis {domain}', tmp)
@@ -332,7 +332,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
for interface in self._interfaces:
# Verify interface changes for disable
- tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd')
+ tmp = self.getFRRconfig(f'interface {interface}', daemon=isis_daemon)
self.assertIn(f'interface {interface}', tmp)
self.assertIn(f' ip router isis {domain}', tmp)
self.assertIn(f' ipv6 router isis {domain}', tmp)
@@ -355,7 +355,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
for level in ['level-1', 'level-2']:
self.cli_set(base_path + ['fast-reroute', 'lfa', 'remote', 'prefix-list', prefix_list, level])
self.cli_commit()
- tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd')
+ tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon)
self.assertIn(f' net {net}', tmp)
self.assertIn(f' fast-reroute remote-lfa prefix-list {prefix_list} {level}', tmp)
self.cli_delete(base_path + ['fast-reroute'])
@@ -365,7 +365,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
for level in ['level-1', 'level-2']:
self.cli_set(base_path + ['fast-reroute', 'lfa', 'local', 'load-sharing', 'disable', level])
self.cli_commit()
- tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd')
+ tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon)
self.assertIn(f' net {net}', tmp)
self.assertIn(f' fast-reroute load-sharing disable {level}', tmp)
self.cli_delete(base_path + ['fast-reroute'])
@@ -376,7 +376,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
for level in ['level-1', 'level-2']:
self.cli_set(base_path + ['fast-reroute', 'lfa', 'local', 'priority-limit', priority, level])
self.cli_commit()
- tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd')
+ tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon)
self.assertIn(f' net {net}', tmp)
self.assertIn(f' fast-reroute priority-limit {priority} {level}', tmp)
self.cli_delete(base_path + ['fast-reroute'])
@@ -388,7 +388,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
for level in ['level-1', 'level-2']:
self.cli_set(base_path + ['fast-reroute', 'lfa', 'local', 'tiebreaker', tiebreaker, 'index', index, level])
self.cli_commit()
- tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd')
+ tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon)
self.assertIn(f' net {net}', tmp)
self.assertIn(f' fast-reroute lfa tiebreaker {tiebreaker} index {index} {level}', tmp)
self.cli_delete(base_path + ['fast-reroute'])
@@ -408,7 +408,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
for topology in topologies:
self.cli_set(base_path + ['topology', topology])
self.cli_commit()
- tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd')
+ tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon)
self.assertIn(f' net {net}', tmp)
self.assertIn(f' topology {topology}', tmp)
diff --git a/smoketest/scripts/cli/test_protocols_mpls.py b/smoketest/scripts/cli/test_protocols_mpls.py
index 0c1599f9b..88528973d 100755
--- a/smoketest/scripts/cli/test_protocols_mpls.py
+++ b/smoketest/scripts/cli/test_protocols_mpls.py
@@ -19,9 +19,9 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Section
+from vyos.frrender import ldpd_daemon
from vyos.utils.process import process_named_running
-PROCESS_NAME = 'ldpd'
base_path = ['protocols', 'mpls', 'ldp']
peers = {
@@ -71,7 +71,7 @@ class TestProtocolsMPLS(VyOSUnitTestSHIM.TestCase):
super(TestProtocolsMPLS, cls).setUpClass()
# Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same
- cls.daemon_pid = process_named_running(PROCESS_NAME)
+ cls.daemon_pid = process_named_running(ldpd_daemon)
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
@@ -82,7 +82,7 @@ class TestProtocolsMPLS(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# check process health and continuity
- self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME))
+ self.assertEqual(self.daemon_pid, process_named_running(ldpd_daemon))
def test_mpls_basic(self):
router_id = '1.2.3.4'
@@ -106,12 +106,12 @@ class TestProtocolsMPLS(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Validate configuration
- frrconfig = self.getFRRconfig('mpls ldp', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('mpls ldp', daemon=ldpd_daemon)
self.assertIn(f'mpls ldp', frrconfig)
self.assertIn(f' router-id {router_id}', frrconfig)
# Validate AFI IPv4
- afiv4_config = self.getFRRconfig(' address-family ipv4', daemon=PROCESS_NAME)
+ afiv4_config = self.getFRRconfig(' address-family ipv4', daemon=ldpd_daemon)
self.assertIn(f' discovery transport-address {transport_ipv4_addr}', afiv4_config)
for interface in interfaces:
self.assertIn(f' interface {interface}', afiv4_config)
diff --git a/smoketest/scripts/cli/test_protocols_openfabric.py b/smoketest/scripts/cli/test_protocols_openfabric.py
index e37aed456..f1372d7ab 100644
--- a/smoketest/scripts/cli/test_protocols_openfabric.py
+++ b/smoketest/scripts/cli/test_protocols_openfabric.py
@@ -19,8 +19,8 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
from vyos.utils.process import process_named_running
+from vyos.frrender import openfabric_daemon
-PROCESS_NAME = 'fabricd'
base_path = ['protocols', 'openfabric']
domain = 'VyOS'
@@ -36,7 +36,7 @@ class TestProtocolsOpenFabric(VyOSUnitTestSHIM.TestCase):
# call base-classes classmethod
super(TestProtocolsOpenFabric, cls).setUpClass()
# Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same
- cls.daemon_pid = process_named_running(PROCESS_NAME)
+ cls.daemon_pid = process_named_running(openfabric_daemon)
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
cls.cli_delete(cls, base_path)
@@ -46,7 +46,7 @@ class TestProtocolsOpenFabric(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# check process health and continuity
- self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME))
+ self.assertEqual(self.daemon_pid, process_named_running(openfabric_daemon))
def openfabric_base_config(self):
self.cli_set(['interfaces', 'dummy', dummy_if])
@@ -75,14 +75,14 @@ class TestProtocolsOpenFabric(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify all changes
- tmp = self.getFRRconfig(f'router openfabric {domain}', daemon='fabricd')
+ tmp = self.getFRRconfig(f'router openfabric {domain}', daemon=openfabric_daemon)
self.assertIn(f' net {net}', tmp)
self.assertIn(f' log-adjacency-changes', tmp)
self.assertIn(f' set-overload-bit', tmp)
self.assertIn(f' fabric-tier {fabric_tier}', tmp)
self.assertIn(f' lsp-gen-interval {lsp_gen_interval}', tmp)
- tmp = self.getFRRconfig(f'interface {dummy_if}', daemon='fabricd')
+ tmp = self.getFRRconfig(f'interface {dummy_if}', daemon=openfabric_daemon)
self.assertIn(f' ip router openfabric {domain}', tmp)
self.assertIn(f' ipv6 router openfabric {domain}', tmp)
@@ -101,12 +101,12 @@ class TestProtocolsOpenFabric(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR openfabric configuration
- tmp = self.getFRRconfig(f'router openfabric {domain}', daemon='fabricd')
+ tmp = self.getFRRconfig(f'router openfabric {domain}', daemon=openfabric_daemon)
self.assertIn(f'router openfabric {domain}', tmp)
self.assertIn(f' net {net}', tmp)
# Verify interface configuration
- tmp = self.getFRRconfig(f'interface {interface}', daemon='fabricd')
+ tmp = self.getFRRconfig(f'interface {interface}', daemon=openfabric_daemon)
self.assertIn(f' ip router openfabric {domain}', tmp)
# for lo interface 'openfabric passive' is implied
self.assertIn(f' openfabric passive', tmp)
@@ -137,11 +137,11 @@ class TestProtocolsOpenFabric(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify all changes
- tmp = self.getFRRconfig(f'router openfabric {domain}', daemon='fabricd')
+ tmp = self.getFRRconfig(f'router openfabric {domain}', daemon=openfabric_daemon)
self.assertIn(f' net {net}', tmp)
self.assertIn(f' domain-password clear {password}', tmp)
- tmp = self.getFRRconfig(f'interface {dummy_if}', daemon='fabricd')
+ tmp = self.getFRRconfig(f'interface {dummy_if}', daemon=openfabric_daemon)
self.assertIn(f' openfabric password clear {password}-{dummy_if}', tmp)
def test_openfabric_multiple_domains(self):
@@ -165,20 +165,20 @@ class TestProtocolsOpenFabric(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR openfabric configuration
- tmp = self.getFRRconfig(f'router openfabric {domain}', daemon='fabricd')
+ tmp = self.getFRRconfig(f'router openfabric {domain}', daemon=openfabric_daemon)
self.assertIn(f'router openfabric {domain}', tmp)
self.assertIn(f' net {net}', tmp)
- tmp = self.getFRRconfig(f'router openfabric {domain_2}', daemon='fabricd')
+ tmp = self.getFRRconfig(f'router openfabric {domain_2}', daemon=openfabric_daemon)
self.assertIn(f'router openfabric {domain_2}', tmp)
self.assertIn(f' net {net}', tmp)
# Verify interface configuration
- tmp = self.getFRRconfig(f'interface {dummy_if}', daemon='fabricd')
+ tmp = self.getFRRconfig(f'interface {dummy_if}', daemon=openfabric_daemon)
self.assertIn(f' ip router openfabric {domain}', tmp)
self.assertIn(f' ipv6 router openfabric {domain}', tmp)
- tmp = self.getFRRconfig(f'interface {interface}', daemon='fabricd')
+ tmp = self.getFRRconfig(f'interface {interface}', daemon=openfabric_daemon)
self.assertIn(f' ip router openfabric {domain_2}', tmp)
diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py
index c3ae54e12..599bf3930 100755
--- a/smoketest/scripts/cli/test_protocols_ospf.py
+++ b/smoketest/scripts/cli/test_protocols_ospf.py
@@ -15,15 +15,16 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
-import time
+from time import sleep
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Section
+from vyos.frrender import ospf_daemon
from vyos.utils.process import process_named_running
+from vyos.xml_ref import default_value
-PROCESS_NAME = 'ospfd'
base_path = ['protocols', 'ospf']
route_map = 'foo-bar-baz10'
@@ -35,7 +36,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
super(TestProtocolsOSPF, cls).setUpClass()
# Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same
- cls.daemon_pid = process_named_running(PROCESS_NAME)
+ cls.daemon_pid = process_named_running(ospf_daemon)
cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit'])
cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '20', 'action', 'permit'])
@@ -55,8 +56,11 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_commit()
+ frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon)
+ self.assertNotIn(f'router ospf', frrconfig)
+
# check process health and continuity
- self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME))
+ self.assertEqual(self.daemon_pid, process_named_running(ospf_daemon))
def test_ospf_01_defaults(self):
# commit changes
@@ -64,7 +68,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig)
self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults
@@ -92,7 +96,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' compatible rfc1583', frrconfig)
self.assertIn(f' auto-cost reference-bandwidth {bandwidth}', frrconfig)
@@ -124,7 +128,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults
for ptotocol in protocols:
@@ -145,7 +149,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults
self.assertIn(f' default-information originate metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig)
@@ -155,7 +159,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon)
self.assertIn(f' default-information originate always metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig)
@@ -197,7 +201,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' mpls-te on', frrconfig)
self.assertIn(f' mpls-te router-address 0.0.0.0', frrconfig) # default
@@ -220,9 +224,16 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['distance', 'ospf', 'inter-area', inter_area])
self.cli_commit()
- frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon)
self.assertIn(f' distance ospf intra-area {intra_area} inter-area {inter_area} external {external}', frrconfig)
+ # https://github.com/FRRouting/frr/issues/17011
+ # We need to wait on_shutdown time, until the OSPF process is removed from the CLI
+ # otherwise the test in tearDown() will fail
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ sleep(int(on_shutdown) + 5) # additional grace period of 5 seconds
def test_ospf_06_neighbor(self):
priority = '10'
@@ -236,7 +247,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon)
self.assertIn(f'router ospf', frrconfig)
for neighbor in neighbors:
self.assertIn(f' neighbor {neighbor} priority {priority} poll-interval {poll_interval}', frrconfig) # default
@@ -255,7 +266,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon)
self.assertIn(f'router ospf', frrconfig)
for protocol in redistribute:
self.assertIn(f' redistribute {protocol} metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig)
@@ -269,6 +280,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
retransmit = '5'
transmit = '5'
dead = '40'
+ window_default = default_value(base_path + ['area', area, 'virtual-link', virtual_link, 'retransmit-window'])
self.cli_set(base_path + ['area', area, 'shortcut', shortcut])
self.cli_set(base_path + ['area', area, 'virtual-link', virtual_link, 'hello-interval', hello])
@@ -282,10 +294,10 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' area {area} shortcut {shortcut}', frrconfig)
- self.assertIn(f' area {area} virtual-link {virtual_link} hello-interval {hello} retransmit-interval {retransmit} transmit-delay {transmit} dead-interval {dead}', frrconfig)
+ self.assertIn(f' area {area} virtual-link {virtual_link} hello-interval {hello} retransmit-interval {retransmit} retransmit-window {window_default} transmit-delay {transmit} dead-interval {dead}', frrconfig)
for network in networks:
self.assertIn(f' network {network} area {area}', frrconfig)
@@ -314,7 +326,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
# commit changes
self.cli_commit()
- frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' passive-interface default', frrconfig)
@@ -338,7 +350,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
for interface in interfaces:
# T5467: It must also be removed from FRR config
- frrconfig = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig(f'interface {interface}', daemon=ospf_daemon)
self.assertNotIn(f'interface {interface}', frrconfig)
# There should be no OSPF related command at all under the interface
self.assertNotIn(f' ip ospf', frrconfig)
@@ -359,11 +371,11 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon)
self.assertIn(f'router ospf', frrconfig)
for interface in interfaces:
- config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ config = self.getFRRconfig(f'interface {interface}', daemon=ospf_daemon)
self.assertIn(f'interface {interface}', config)
self.assertIn(f' ip ospf area {area}', config)
@@ -386,17 +398,17 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig)
self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults
- frrconfig = self.getFRRconfig(f'router ospf vrf {vrf}', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig(f'router ospf vrf {vrf}', daemon=ospf_daemon)
self.assertIn(f'router ospf vrf {vrf}', frrconfig)
self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig)
self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults
- frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=ospf_daemon)
self.assertIn(f'interface {vrf_iface}', frrconfig)
self.assertIn(f' ip ospf area {area}', frrconfig)
@@ -406,7 +418,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# T5467: It must also be removed from FRR config
- frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=ospf_daemon)
self.assertNotIn(f'interface {vrf_iface}', frrconfig)
# There should be no OSPF related command at all under the interface
self.assertNotIn(f' ip ospf', frrconfig)
@@ -432,7 +444,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # default
self.assertIn(f' network {network} area {area}', frrconfig)
@@ -465,7 +477,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify all changes
- frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon)
self.assertIn(f' segment-routing on', frrconfig)
self.assertIn(f' segment-routing global-block {global_block_low} {global_block_high} local-block {local_block_low} {local_block_high}', frrconfig)
self.assertIn(f' segment-routing node-msd {maximum_stack_size}', frrconfig)
@@ -483,7 +495,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify main OSPF changes
- frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig)
self.assertIn(f' mpls ldp-sync holddown {holddown}', frrconfig)
@@ -496,7 +508,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
for interface in interfaces:
# Verify interface changes for holddown
- config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ config = self.getFRRconfig(f'interface {interface}', daemon=ospf_daemon)
self.assertIn(f'interface {interface}', config)
self.assertIn(f' ip ospf dead-interval 40', config)
self.assertIn(f' ip ospf mpls ldp-sync', config)
@@ -510,7 +522,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
for interface in interfaces:
# Verify interface changes for disable
- config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ config = self.getFRRconfig(f'interface {interface}', daemon=ospf_daemon)
self.assertIn(f'interface {interface}', config)
self.assertIn(f' ip ospf dead-interval 40', config)
self.assertNotIn(f' ip ospf mpls ldp-sync', config)
@@ -532,7 +544,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' capability opaque', frrconfig)
self.assertIn(f' graceful-restart grace-period {period}', frrconfig)
@@ -558,23 +570,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
- # Required to prevent the race condition T6761
- retry_count = 0
- max_retries = 60
-
- while not frrconfig and retry_count < max_retries:
- # Log every 10 seconds
- if retry_count % 10 == 0:
- print(f"Attempt {retry_count}: FRR config is still empty. Retrying...")
-
- retry_count += 1
- time.sleep(1)
- frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
-
- if not frrconfig:
- print("Failed to retrieve FRR config after 60 seconds")
-
+ frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon, empty_retry=60)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' network {network} area {area1}', frrconfig)
diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py
index 989e1552d..d961a4fdc 100755
--- a/smoketest/scripts/cli/test_protocols_ospfv3.py
+++ b/smoketest/scripts/cli/test_protocols_ospfv3.py
@@ -20,9 +20,9 @@ from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Section
+from vyos.frrender import ospf6_daemon
from vyos.utils.process import process_named_running
-PROCESS_NAME = 'ospf6d'
base_path = ['protocols', 'ospfv3']
route_map = 'foo-bar-baz-0815'
@@ -36,7 +36,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
super(TestProtocolsOSPFv3, cls).setUpClass()
# Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same
- cls.daemon_pid = process_named_running(PROCESS_NAME)
+ cls.daemon_pid = process_named_running(ospf6_daemon)
cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit'])
cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '20', 'action', 'permit'])
@@ -54,8 +54,11 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_commit()
+ frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon)
+ self.assertNotIn(f'router ospf6', frrconfig)
+
# check process health and continuity
- self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME))
+ self.assertEqual(self.daemon_pid, process_named_running(ospf6_daemon))
def test_ospfv3_01_basic(self):
seq = '10'
@@ -78,7 +81,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon)
self.assertIn(f'router ospf6', frrconfig)
self.assertIn(f' area {default_area} range {prefix}', frrconfig)
self.assertIn(f' ospf6 router-id {router_id}', frrconfig)
@@ -86,7 +89,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' area {default_area} export-list {acl_name}', frrconfig)
for interface in interfaces:
- if_config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ if_config = self.getFRRconfig(f'interface {interface}', daemon=ospf6_daemon)
self.assertIn(f'ipv6 ospf6 area {default_area}', if_config)
self.cli_delete(['policy', 'access-list6', acl_name])
@@ -107,7 +110,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon)
self.assertIn(f'router ospf6', frrconfig)
self.assertIn(f' distance {dist_global}', frrconfig)
self.assertIn(f' distance ospf6 intra-area {dist_intra_area} inter-area {dist_inter_area} external {dist_external}', frrconfig)
@@ -131,7 +134,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon)
self.assertIn(f'router ospf6', frrconfig)
for protocol in redistribute:
self.assertIn(f' redistribute {protocol} metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig)
@@ -162,13 +165,13 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon)
self.assertIn(f'router ospf6', frrconfig)
cost = '100'
priority = '10'
for interface in interfaces:
- if_config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ if_config = self.getFRRconfig(f'interface {interface}', daemon=ospf6_daemon)
self.assertIn(f'interface {interface}', if_config)
self.assertIn(f' ipv6 ospf6 bfd', if_config)
self.assertIn(f' ipv6 ospf6 bfd profile {bfd_profile}', if_config)
@@ -185,7 +188,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
for interface in interfaces:
- if_config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ if_config = self.getFRRconfig(f'interface {interface}', daemon=ospf6_daemon)
# There should be no OSPF6 configuration at all after interface removal
self.assertNotIn(f' ipv6 ospf6', if_config)
@@ -201,7 +204,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon)
self.assertIn(f'router ospf6', frrconfig)
self.assertIn(f' area {area_stub} stub', frrconfig)
self.assertIn(f' area {area_stub_nosum} stub no-summary', frrconfig)
@@ -227,7 +230,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon)
self.assertIn(f'router ospf6', frrconfig)
self.assertIn(f' area {area_nssa} nssa', frrconfig)
self.assertIn(f' area {area_nssa_nosum} nssa default-information-originate no-summary', frrconfig)
@@ -247,7 +250,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon)
self.assertIn(f'router ospf6', frrconfig)
self.assertIn(f' default-information originate metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig)
@@ -256,7 +259,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon)
self.assertIn(f' default-information originate always metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig)
@@ -282,15 +285,15 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon)
self.assertIn(f'router ospf6', frrconfig)
self.assertIn(f' ospf6 router-id {router_id}', frrconfig)
- frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=ospf6_daemon)
self.assertIn(f'interface {vrf_iface}', frrconfig)
self.assertIn(f' ipv6 ospf6 bfd', frrconfig)
- frrconfig = self.getFRRconfig(f'router ospf6 vrf {vrf}', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig(f'router ospf6 vrf {vrf}', daemon=ospf6_daemon)
self.assertIn(f'router ospf6 vrf {vrf}', frrconfig)
self.assertIn(f' ospf6 router-id {router_id_vrf}', frrconfig)
@@ -300,7 +303,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# T5467: It must also be removed from FRR config
- frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=ospf6_daemon)
self.assertNotIn(f'interface {vrf_iface}', frrconfig)
# There should be no OSPF related command at all under the interface
self.assertNotIn(f' ipv6 ospf6', frrconfig)
@@ -326,7 +329,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon)
self.assertIn(f'router ospf6', frrconfig)
self.assertIn(f' graceful-restart grace-period {period}', frrconfig)
self.assertIn(f' graceful-restart helper planned-only', frrconfig)
diff --git a/smoketest/scripts/cli/test_protocols_pim.py b/smoketest/scripts/cli/test_protocols_pim.py
index ccfced138..98b9d57aa 100755
--- a/smoketest/scripts/cli/test_protocols_pim.py
+++ b/smoketest/scripts/cli/test_protocols_pim.py
@@ -19,22 +19,22 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
+from vyos.frrender import pim_daemon
from vyos.ifconfig import Section
from vyos.utils.process import process_named_running
-PROCESS_NAME = 'pimd'
base_path = ['protocols', 'pim']
class TestProtocolsPIM(VyOSUnitTestSHIM.TestCase):
def tearDown(self):
# pimd process must be running
- self.assertTrue(process_named_running(PROCESS_NAME))
+ self.assertTrue(process_named_running(pim_daemon))
self.cli_delete(base_path)
self.cli_commit()
# pimd process must be stopped by now
- self.assertFalse(process_named_running(PROCESS_NAME))
+ self.assertFalse(process_named_running(pim_daemon))
def test_01_pim_basic(self):
rp = '127.0.0.1'
@@ -57,11 +57,11 @@ class TestProtocolsPIM(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR pimd configuration
- frrconfig = self.getFRRconfig(daemon=PROCESS_NAME)
- self.assertIn(f'ip pim rp {rp} {group}', frrconfig)
+ frrconfig = self.getFRRconfig('router pim', endsection='^exit', daemon=pim_daemon)
+ self.assertIn(f' rp {rp} {group}', frrconfig)
for interface in interfaces:
- frrconfig = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig(f'interface {interface}', daemon=pim_daemon)
self.assertIn(f'interface {interface}', frrconfig)
self.assertIn(f' ip pim', frrconfig)
self.assertIn(f' ip pim bfd', frrconfig)
@@ -108,18 +108,18 @@ class TestProtocolsPIM(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR pimd configuration
- frrconfig = self.getFRRconfig(daemon=PROCESS_NAME)
- self.assertIn(f'ip pim rp {rp} {group}', frrconfig)
- self.assertIn(f'ip pim rp keep-alive-timer {rp_keep_alive_timer}', frrconfig)
- self.assertIn(f'ip pim ecmp rebalance', frrconfig)
- self.assertIn(f'ip pim join-prune-interval {join_prune_interval}', frrconfig)
- self.assertIn(f'ip pim keep-alive-timer {keep_alive_timer}', frrconfig)
- self.assertIn(f'ip pim packets {packets}', frrconfig)
- self.assertIn(f'ip pim register-accept-list {prefix_list}', frrconfig)
- self.assertIn(f'ip pim register-suppress-time {register_suppress_time}', frrconfig)
- self.assertIn(f'no ip pim send-v6-secondary', frrconfig)
- self.assertIn(f'ip pim spt-switchover infinity-and-beyond prefix-list {prefix_list}', frrconfig)
- self.assertIn(f'ip pim ssm prefix-list {prefix_list}', frrconfig)
+ frrconfig = self.getFRRconfig('router pim', endsection='^exit', daemon=pim_daemon)
+ self.assertIn(f' no send-v6-secondary', frrconfig)
+ self.assertIn(f' rp {rp} {group}', frrconfig)
+ self.assertIn(f' register-suppress-time {register_suppress_time}', frrconfig)
+ self.assertIn(f' join-prune-interval {join_prune_interval}', frrconfig)
+ self.assertIn(f' packets {packets}', frrconfig)
+ self.assertIn(f' keep-alive-timer {keep_alive_timer}', frrconfig)
+ self.assertIn(f' rp keep-alive-timer {rp_keep_alive_timer}', frrconfig)
+ self.assertIn(f' ssm prefix-list {prefix_list}', frrconfig)
+ self.assertIn(f' register-accept-list {prefix_list}', frrconfig)
+ self.assertIn(f' spt-switchover infinity-and-beyond prefix-list {prefix_list}', frrconfig)
+ self.assertIn(f' ecmp rebalance', frrconfig)
def test_03_pim_igmp_proxy(self):
igmp_proxy = ['protocols', 'igmp-proxy']
@@ -170,11 +170,11 @@ class TestProtocolsPIM(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
- frrconfig = self.getFRRconfig(daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig(daemon=pim_daemon)
self.assertIn(f'ip igmp watermark-warn {watermark_warning}', frrconfig)
for interface in interfaces:
- frrconfig = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ frrconfig = self.getFRRconfig(f'interface {interface}', daemon=pim_daemon)
self.assertIn(f'interface {interface}', frrconfig)
self.assertIn(f' ip igmp', frrconfig)
self.assertIn(f' ip igmp version {version}', frrconfig)
@@ -184,9 +184,9 @@ class TestProtocolsPIM(VyOSUnitTestSHIM.TestCase):
for join, join_config in igmp_join.items():
if 'source' in join_config:
for source in join_config['source']:
- self.assertIn(f' ip igmp join {join} {source}', frrconfig)
+ self.assertIn(f' ip igmp join-group {join} {source}', frrconfig)
else:
- self.assertIn(f' ip igmp join {join}', frrconfig)
+ self.assertIn(f' ip igmp join-group {join}', frrconfig)
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_protocols_pim6.py b/smoketest/scripts/cli/test_protocols_pim6.py
index ba24edca2..f69eb4ae1 100755
--- a/smoketest/scripts/cli/test_protocols_pim6.py
+++ b/smoketest/scripts/cli/test_protocols_pim6.py
@@ -19,9 +19,9 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Section
+from vyos.frrender import pim6_daemon
from vyos.utils.process import process_named_running
-PROCESS_NAME = 'pim6d'
base_path = ['protocols', 'pim6']
class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase):
@@ -30,7 +30,7 @@ class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase):
# call base-classes classmethod
super(TestProtocolsPIMv6, cls).setUpClass()
# Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same
- cls.daemon_pid = process_named_running(PROCESS_NAME)
+ cls.daemon_pid = process_named_running(pim6_daemon)
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
cls.cli_delete(cls, base_path)
@@ -40,7 +40,7 @@ class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# check process health and continuity
- self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME))
+ self.assertEqual(self.daemon_pid, process_named_running(pim6_daemon))
def test_pim6_01_mld_simple(self):
# commit changes
@@ -52,7 +52,7 @@ class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase):
# Verify FRR pim6d configuration
for interface in interfaces:
- config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ config = self.getFRRconfig(f'interface {interface}', daemon=pim6_daemon)
self.assertIn(f'interface {interface}', config)
self.assertIn(f' ipv6 mld', config)
self.assertNotIn(f' ipv6 mld version 1', config)
@@ -65,7 +65,7 @@ class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase):
# Verify FRR pim6d configuration
for interface in interfaces:
- config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ config = self.getFRRconfig(f'interface {interface}', daemon=pim6_daemon)
self.assertIn(f'interface {interface}', config)
self.assertIn(f' ipv6 mld', config)
self.assertIn(f' ipv6 mld version 1', config)
@@ -88,9 +88,9 @@ class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase):
# Verify FRR pim6d configuration
for interface in interfaces:
- config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ config = self.getFRRconfig(f'interface {interface}', daemon=pim6_daemon)
self.assertIn(f'interface {interface}', config)
- self.assertIn(f' ipv6 mld join ff18::1234', config)
+ self.assertIn(f' ipv6 mld join-group ff18::1234', config)
# Join a source-specific multicast group
for interface in interfaces:
@@ -100,9 +100,9 @@ class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase):
# Verify FRR pim6d configuration
for interface in interfaces:
- config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ config = self.getFRRconfig(f'interface {interface}', daemon=pim6_daemon)
self.assertIn(f'interface {interface}', config)
- self.assertIn(f' ipv6 mld join ff38::5678 2001:db8::5678', config)
+ self.assertIn(f' ipv6 mld join-group ff38::5678 2001:db8::5678', config)
def test_pim6_03_basic(self):
interfaces = Section.interfaces('ethernet')
@@ -128,14 +128,14 @@ class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR pim6d configuration
- config = self.getFRRconfig(daemon=PROCESS_NAME)
- self.assertIn(f'ipv6 pim join-prune-interval {join_prune_interval}', config)
- self.assertIn(f'ipv6 pim keep-alive-timer {keep_alive_timer}', config)
- self.assertIn(f'ipv6 pim packets {packets}', config)
- self.assertIn(f'ipv6 pim register-suppress-time {register_suppress_time}', config)
+ config = self.getFRRconfig('router pim6', endsection='^exit', daemon=pim6_daemon)
+ self.assertIn(f' join-prune-interval {join_prune_interval}', config)
+ self.assertIn(f' keep-alive-timer {keep_alive_timer}', config)
+ self.assertIn(f' packets {packets}', config)
+ self.assertIn(f' register-suppress-time {register_suppress_time}', config)
for interface in interfaces:
- config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ config = self.getFRRconfig(f'interface {interface}', daemon=pim6_daemon)
self.assertIn(f' ipv6 pim drpriority {dr_priority}', config)
self.assertIn(f' ipv6 pim hello {hello}', config)
self.assertIn(f' no ipv6 pim bsm', config)
diff --git a/smoketest/scripts/cli/test_protocols_rip.py b/smoketest/scripts/cli/test_protocols_rip.py
index bfc327fd4..33706a9c9 100755
--- a/smoketest/scripts/cli/test_protocols_rip.py
+++ b/smoketest/scripts/cli/test_protocols_rip.py
@@ -19,9 +19,9 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.ifconfig import Section
+from vyos.frrender import rip_daemon
from vyos.utils.process import process_named_running
-PROCESS_NAME = 'ripd'
acl_in = '198'
acl_out = '199'
prefix_list_in = 'foo-prefix'
@@ -35,7 +35,7 @@ class TestProtocolsRIP(VyOSUnitTestSHIM.TestCase):
def setUpClass(cls):
super(TestProtocolsRIP, cls).setUpClass()
# Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same
- cls.daemon_pid = process_named_running(PROCESS_NAME)
+ cls.daemon_pid = process_named_running(rip_daemon)
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
cls.cli_delete(cls, base_path)
@@ -66,8 +66,11 @@ class TestProtocolsRIP(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_commit()
+ frrconfig = self.getFRRconfig('router rip')
+ self.assertNotIn(f'router rip', frrconfig)
+
# check process health and continuity
- self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME))
+ self.assertEqual(self.daemon_pid, process_named_running(rip_daemon))
def test_rip_01_parameters(self):
distance = '40'
diff --git a/smoketest/scripts/cli/test_protocols_ripng.py b/smoketest/scripts/cli/test_protocols_ripng.py
index 0cfb065c6..b10df9679 100755
--- a/smoketest/scripts/cli/test_protocols_ripng.py
+++ b/smoketest/scripts/cli/test_protocols_ripng.py
@@ -19,9 +19,9 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.ifconfig import Section
+from vyos.frrender import ripng_daemon
from vyos.utils.process import process_named_running
-PROCESS_NAME = 'ripngd'
acl_in = '198'
acl_out = '199'
prefix_list_in = 'foo-prefix'
@@ -36,7 +36,7 @@ class TestProtocolsRIPng(VyOSUnitTestSHIM.TestCase):
# call base-classes classmethod
super(TestProtocolsRIPng, cls).setUpClass()
# Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same
- cls.daemon_pid = process_named_running(PROCESS_NAME)
+ cls.daemon_pid = process_named_running(ripng_daemon)
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
cls.cli_delete(cls, base_path)
@@ -66,8 +66,11 @@ class TestProtocolsRIPng(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_commit()
+ frrconfig = self.getFRRconfig('router ripng')
+ self.assertNotIn(f'router ripng', frrconfig)
+
# check process health and continuity
- self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME))
+ self.assertEqual(self.daemon_pid, process_named_running(ripng_daemon))
def test_ripng_01_parameters(self):
metric = '8'
diff --git a/smoketest/scripts/cli/test_protocols_rpki.py b/smoketest/scripts/cli/test_protocols_rpki.py
index 29f03a26a..23738717a 100755
--- a/smoketest/scripts/cli/test_protocols_rpki.py
+++ b/smoketest/scripts/cli/test_protocols_rpki.py
@@ -19,12 +19,11 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
+from vyos.frrender import bgp_daemon
from vyos.utils.file import read_file
from vyos.utils.process import process_named_running
base_path = ['protocols', 'rpki']
-PROCESS_NAME = 'bgpd'
-
rpki_key_name = 'rpki-smoketest'
rpki_key_type = 'ssh-rsa'
@@ -108,7 +107,7 @@ class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase):
# call base-classes classmethod
super(TestProtocolsRPKI, cls).setUpClass()
# Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same
- cls.daemon_pid = process_named_running(PROCESS_NAME)
+ cls.daemon_pid = process_named_running(bgp_daemon)
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
cls.cli_delete(cls, base_path)
@@ -117,8 +116,11 @@ class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_commit()
+ frrconfig = self.getFRRconfig('rpki')
+ self.assertNotIn(f'rpki', frrconfig)
+
# check process health and continuity
- self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME))
+ self.assertEqual(self.daemon_pid, process_named_running(bgp_daemon))
def test_rpki(self):
expire_interval = '3600'
@@ -159,7 +161,7 @@ class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase):
for peer, peer_config in cache.items():
port = peer_config['port']
preference = peer_config['preference']
- self.assertIn(f'rpki cache {peer} {port} preference {preference}', frrconfig)
+ self.assertIn(f'rpki cache tcp {peer} {port} preference {preference}', frrconfig)
def test_rpki_ssh(self):
polling = '7200'
@@ -195,7 +197,7 @@ class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase):
port = cache_config['port']
preference = cache_config['preference']
username = cache_config['username']
- self.assertIn(f'rpki cache {cache_name} {port} {username} /run/frr/id_rpki_{cache_name} /run/frr/id_rpki_{cache_name}.pub preference {preference}', frrconfig)
+ self.assertIn(f'rpki cache ssh {cache_name} {port} {username} /run/frr/id_rpki_{cache_name} /run/frr/id_rpki_{cache_name}.pub preference {preference}', frrconfig)
# Verify content of SSH keys
tmp = read_file(f'/run/frr/id_rpki_{cache_name}')
@@ -213,7 +215,7 @@ class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase):
port = cache_config['port']
preference = cache_config['preference']
username = cache_config['username']
- self.assertIn(f'rpki cache {cache_name} {port} {username} /run/frr/id_rpki_{cache_name} /run/frr/id_rpki_{cache_name}.pub preference {preference}', frrconfig)
+ self.assertIn(f'rpki cache ssh {cache_name} {port} {username} /run/frr/id_rpki_{cache_name} /run/frr/id_rpki_{cache_name}.pub preference {preference}', frrconfig)
# Verify content of SSH keys
tmp = read_file(f'/run/frr/id_rpki_{cache_name}')
diff --git a/smoketest/scripts/cli/test_protocols_segment-routing.py b/smoketest/scripts/cli/test_protocols_segment-routing.py
index daa7f088f..624985476 100755
--- a/smoketest/scripts/cli/test_protocols_segment-routing.py
+++ b/smoketest/scripts/cli/test_protocols_segment-routing.py
@@ -20,11 +20,11 @@ from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Section
+from vyos.frrender import zebra_daemon
from vyos.utils.process import process_named_running
from vyos.utils.system import sysctl_read
base_path = ['protocols', 'segment-routing']
-PROCESS_NAME = 'zebra'
class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase):
@classmethod
@@ -32,7 +32,7 @@ class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase):
# call base-classes classmethod
super(TestProtocolsSegmentRouting, cls).setUpClass()
# Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same
- cls.daemon_pid = process_named_running(PROCESS_NAME)
+ cls.daemon_pid = process_named_running(zebra_daemon)
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
cls.cli_delete(cls, base_path)
@@ -42,7 +42,7 @@ class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# check process health and continuity
- self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME))
+ self.assertEqual(self.daemon_pid, process_named_running(zebra_daemon))
def test_srv6(self):
interfaces = Section.interfaces('ethernet', vlan=False)
@@ -68,7 +68,7 @@ class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase):
self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1')
self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '0') # default
- frrconfig = self.getFRRconfig(f'segment-routing', daemon='zebra')
+ frrconfig = self.getFRRconfig(f'segment-routing', daemon=zebra_daemon)
self.assertIn(f'segment-routing', frrconfig)
self.assertIn(f' srv6', frrconfig)
self.assertIn(f' locators', frrconfig)
diff --git a/smoketest/scripts/cli/test_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py
index f676e2a52..a2cde0237 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,11 @@ 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', '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 +261,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 +472,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 +524,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 + ['mroute', 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 + ['mroute', 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/smoketest/scripts/cli/test_qos.py b/smoketest/scripts/cli/test_qos.py
index 79b791288..231743344 100755
--- a/smoketest/scripts/cli/test_qos.py
+++ b/smoketest/scripts/cli/test_qos.py
@@ -27,6 +27,7 @@ from vyos.utils.process import cmd
base_path = ['qos']
+
def get_tc_qdisc_json(interface, all=False) -> dict:
tmp = cmd(f'tc -detail -json qdisc show dev {interface}')
tmp = loads(tmp)
@@ -934,6 +935,81 @@ class TestQoS(VyOSUnitTestSHIM.TestCase):
self.assertEqual(nat, tmp['options']['nat'])
nat = not nat
+ def test_18_priority_queue_default(self):
+ interface = self._interfaces[0]
+ policy_name = f'qos-policy-{interface}'
+
+ self.cli_set(base_path + ['interface', interface, 'egress', policy_name])
+ self.cli_set(
+ base_path
+ + ['policy', 'priority-queue', policy_name, 'description', 'default policy']
+ )
+
+ self.cli_commit()
+
+ tmp = get_tc_qdisc_json(interface, all=True)
+
+ self.assertEqual(2, len(tmp))
+ self.assertEqual('prio', tmp[0]['kind'])
+ self.assertDictEqual(
+ {
+ 'bands': 2,
+ 'priomap': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
+ 'multiqueue': False,
+ },
+ tmp[0]['options'],
+ )
+ self.assertEqual('pfifo', tmp[1]['kind'])
+ self.assertDictEqual({'limit': 1000}, tmp[1]['options'])
+
+ def test_19_priority_queue_default_random_detect(self):
+ interface = self._interfaces[0]
+ policy_name = f'qos-policy-{interface}'
+
+ self.cli_set(base_path + ['interface', interface, 'egress', policy_name])
+ self.cli_set(
+ base_path
+ + [
+ 'policy',
+ 'priority-queue',
+ policy_name,
+ 'default',
+ 'queue-type',
+ 'random-detect',
+ ]
+ )
+
+ self.cli_commit()
+
+ tmp = get_tc_qdisc_json(interface, all=True)
+
+ self.assertEqual(2, len(tmp))
+ self.assertEqual('prio', tmp[0]['kind'])
+ self.assertDictEqual(
+ {
+ 'bands': 2,
+ 'priomap': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
+ 'multiqueue': False,
+ },
+ tmp[0]['options'],
+ )
+ self.assertEqual('red', tmp[1]['kind'])
+ self.assertDictEqual(
+ {
+ 'limit': 73728,
+ 'min': 9216,
+ 'max': 18432,
+ 'ecn': False,
+ 'harddrop': False,
+ 'adaptive': False,
+ 'nodrop': False,
+ 'ewma': 3,
+ 'probability': 0.1,
+ 'Scell_log': 13,
+ },
+ tmp[1]['options'],
+ )
+
def test_20_round_robin_policy_default(self):
interface = self._interfaces[0]
policy_name = f'qos-policy-{interface}'
@@ -1161,6 +1237,72 @@ class TestQoS(VyOSUnitTestSHIM.TestCase):
self.assertIn('filter parent ffff: protocol all pref 255 basic chain 0', tc_filters)
self.assertIn('action order 1: police 0x2 rate 1Gbit burst 125000000b mtu 2Kb action drop overhead 0b', tc_filters)
+ def test_24_policy_shaper_match_ether(self):
+ interface = self._interfaces[0]
+ bandwidth = 250
+ default_bandwidth = 20
+ default_ceil = 30
+ class_bandwidth = 50
+ class_ceil = 80
+
+ shaper_name = f'qos-shaper-{interface}'
+
+ self.cli_set(base_path + ['interface', interface, 'egress', shaper_name])
+ self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'bandwidth', f'{bandwidth}mbit'])
+ self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'bandwidth', f'{default_bandwidth}mbit'])
+ self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'ceiling', f'{default_ceil}mbit'])
+ self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'queue-type', 'fair-queue'])
+ self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '23', 'bandwidth', f'{class_bandwidth}mbit'])
+ self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '23', 'ceiling', f'{class_ceil}mbit'])
+ self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '23', 'match', '10', 'ether', 'protocol', 'all'])
+ self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '23', 'match', '10', 'ether', 'destination', '0c:89:0a:2e:00:00'])
+ self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '23', 'match', '10', 'ether', 'source', '0c:89:0a:2e:00:01'])
+
+ # commit changes
+ self.cli_commit()
+
+ config_entries = (
+ f'root rate {bandwidth}Mbit ceil {bandwidth}Mbit',
+ f'prio 0 rate {class_bandwidth}Mbit ceil {class_ceil}Mbit',
+ f'prio 7 rate {default_bandwidth}Mbit ceil {default_ceil}Mbit'
+ )
+
+ output = cmd(f'tc class show dev {interface}')
+
+ for config_entry in config_entries:
+ self.assertIn(config_entry, output)
+
+ filter = get_tc_filter_details(interface)
+ self.assertIn('match 0c890a2e/ffffffff at -8', filter)
+ self.assertIn('match 00010000/ffff0000 at -4', filter)
+ self.assertIn('match 00000c89/0000ffff at -16', filter)
+ self.assertIn('match 0a2e0000/ffffffff at -12', filter)
+
+ for proto in ['802.1Q', '802_2', '802_3', 'aarp', 'aoe', 'arp', 'atalk',
+ 'dec', 'ip', 'ipv6', 'ipx', 'lat', 'localtalk', 'rarp',
+ 'snap', 'x25', 1, 255, 65535]:
+ self.cli_set(
+ base_path + ['policy', 'shaper', shaper_name, 'class', '23',
+ 'match', '10', 'ether', 'protocol', str(proto)])
+ self.cli_commit()
+
+ if isinstance(proto, int):
+ if proto == 1:
+ self.assertIn(f'filter parent 1: protocol 802_3 pref',
+ get_tc_filter_details(interface))
+ else:
+ self.assertIn(f'filter parent 1: protocol [{proto}] pref',
+ get_tc_filter_details(interface))
+
+ elif proto == '0x000C':
+ # see other codes in the iproute2 eg https://github.com/iproute2/iproute2/blob/413cf4f03a9b6a219c94b86f41d67992b0a14b82/include/uapi/linux/if_ether.h#L130
+ self.assertIn(f'filter parent 1: protocol can pref',
+ get_tc_filter_details(interface))
+
+ else:
+ self.assertIn(f'filter parent 1: protocol {proto} pref',
+ get_tc_filter_details(interface))
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_ipoe-server.py b/smoketest/scripts/cli/test_service_ipoe-server.py
index be03179bf..ab0898d17 100755
--- a/smoketest/scripts/cli/test_service_ipoe-server.py
+++ b/smoketest/scripts/cli/test_service_ipoe-server.py
@@ -260,6 +260,41 @@ delegate={delegate_2_prefix},{delegate_mask},name={pool_name}"""
tmp = ','.join(vlans)
self.assertIn(f'{interface},{tmp}', conf['ipoe']['vlan-mon'])
+ def test_ipoe_server_static_client_ip(self):
+ mac_address = '08:00:27:2f:d8:06'
+ ip_address = '192.0.2.100'
+
+ # Test configuration of local authentication for PPPoE server
+ self.basic_config()
+ # Rewrite authentication from basic_config
+ self.set(
+ [
+ 'authentication',
+ 'interface',
+ interface,
+ 'mac',
+ mac_address,
+ 'static-ip',
+ ip_address,
+ ]
+ )
+ self.set(['authentication', 'mode', 'local'])
+ # commit changes
+ self.cli_commit()
+
+ # Validate configuration values
+ conf = ConfigParser(allow_no_value=True, delimiters='=', strict=False)
+ conf.read(self._config_file)
+
+ # basic verification
+ self.verify(conf)
+
+ # check local users
+ tmp = cmd(f'sudo cat {self._chap_secrets}')
+ regex = f'{interface}\s+\*\s+{mac_address}\s+{ip_address}'
+ tmp = re.findall(regex, tmp)
+ self.assertTrue(tmp)
+
@unittest.skip("PPP is not a part of IPoE")
def test_accel_ppp_options(self):
pass
diff --git a/smoketest/scripts/cli/test_system_ip.py b/smoketest/scripts/cli/test_system_ip.py
index 5b0090237..7d730f7b2 100755
--- a/smoketest/scripts/cli/test_system_ip.py
+++ b/smoketest/scripts/cli/test_system_ip.py
@@ -18,11 +18,21 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
-from vyos.utils.file import read_file
+from vyos.utils.system import sysctl_read
+from vyos.xml_ref import default_value
+from vyos.frrender import mgmt_daemon
+from vyos.frrender import zebra_daemon
base_path = ['system', 'ip']
class TestSystemIP(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ super(TestSystemIP, cls).setUpClass()
+ # ensure we can also run this test on a live system - so lets clean
+ # out the current configuration :)
+ cls.cli_delete(cls, base_path)
+
def tearDown(self):
self.cli_delete(base_path)
self.cli_commit()
@@ -30,47 +40,45 @@ class TestSystemIP(VyOSUnitTestSHIM.TestCase):
def test_system_ip_forwarding(self):
# Test if IPv4 forwarding can be disabled globally, default is '1'
# which means forwarding enabled
- all_forwarding = '/proc/sys/net/ipv4/conf/all/forwarding'
- self.assertEqual(read_file(all_forwarding), '1')
+ self.assertEqual(sysctl_read('net.ipv4.conf.all.forwarding'), '1')
self.cli_set(base_path + ['disable-forwarding'])
self.cli_commit()
+ self.assertEqual(sysctl_read('net.ipv4.conf.all.forwarding'), '0')
+ frrconfig = self.getFRRconfig('', end='', daemon=zebra_daemon)
+ self.assertIn('no ip forwarding', frrconfig)
- self.assertEqual(read_file(all_forwarding), '0')
+ self.cli_delete(base_path + ['disable-forwarding'])
+ self.cli_commit()
+ self.assertEqual(sysctl_read('net.ipv4.conf.all.forwarding'), '1')
+ frrconfig = self.getFRRconfig('', end='', daemon=zebra_daemon)
+ self.assertNotIn('no ip forwarding', frrconfig)
def test_system_ip_multipath(self):
# Test IPv4 multipathing options, options default to off -> '0'
- use_neigh = '/proc/sys/net/ipv4/fib_multipath_use_neigh'
- hash_policy = '/proc/sys/net/ipv4/fib_multipath_hash_policy'
-
- self.assertEqual(read_file(use_neigh), '0')
- self.assertEqual(read_file(hash_policy), '0')
+ self.assertEqual(sysctl_read('net.ipv4.fib_multipath_use_neigh'), '0')
+ self.assertEqual(sysctl_read('net.ipv4.fib_multipath_hash_policy'), '0')
self.cli_set(base_path + ['multipath', 'ignore-unreachable-nexthops'])
self.cli_set(base_path + ['multipath', 'layer4-hashing'])
self.cli_commit()
- self.assertEqual(read_file(use_neigh), '1')
- self.assertEqual(read_file(hash_policy), '1')
+ self.assertEqual(sysctl_read('net.ipv4.fib_multipath_use_neigh'), '1')
+ self.assertEqual(sysctl_read('net.ipv4.fib_multipath_hash_policy'), '1')
def test_system_ip_arp_table_size(self):
- # Maximum number of entries to keep in the ARP cache, the
- # default is 8k
+ cli_default = int(default_value(base_path + ['arp', 'table-size']))
+ def _verify_gc_thres(table_size):
+ self.assertEqual(sysctl_read('net.ipv4.neigh.default.gc_thresh3'), str(table_size))
+ self.assertEqual(sysctl_read('net.ipv4.neigh.default.gc_thresh2'), str(table_size // 2))
+ self.assertEqual(sysctl_read('net.ipv4.neigh.default.gc_thresh1'), str(table_size // 8))
- gc_thresh3 = '/proc/sys/net/ipv4/neigh/default/gc_thresh3'
- gc_thresh2 = '/proc/sys/net/ipv4/neigh/default/gc_thresh2'
- gc_thresh1 = '/proc/sys/net/ipv4/neigh/default/gc_thresh1'
- self.assertEqual(read_file(gc_thresh3), '8192')
- self.assertEqual(read_file(gc_thresh2), '4096')
- self.assertEqual(read_file(gc_thresh1), '1024')
+ _verify_gc_thres(cli_default)
for size in [1024, 2048, 4096, 8192, 16384, 32768]:
self.cli_set(base_path + ['arp', 'table-size', str(size)])
self.cli_commit()
-
- self.assertEqual(read_file(gc_thresh3), str(size))
- self.assertEqual(read_file(gc_thresh2), str(size // 2))
- self.assertEqual(read_file(gc_thresh1), str(size // 8))
+ _verify_gc_thres(size)
def test_system_ip_protocol_route_map(self):
protocols = ['any', 'babel', 'bgp', 'connected', 'eigrp', 'isis',
@@ -83,7 +91,7 @@ class TestSystemIP(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify route-map properly applied to FRR
- frrconfig = self.getFRRconfig('ip protocol', end='', daemon='zebra')
+ frrconfig = self.getFRRconfig('ip protocol', end='', daemon=mgmt_daemon)
for protocol in protocols:
self.assertIn(f'ip protocol {protocol} route-map route-map-{protocol}', frrconfig)
@@ -94,7 +102,7 @@ class TestSystemIP(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify route-map properly applied to FRR
- frrconfig = self.getFRRconfig('ip protocol', end='', daemon='zebra')
+ frrconfig = self.getFRRconfig('ip protocol', end='', daemon=mgmt_daemon)
self.assertNotIn(f'ip protocol', frrconfig)
def test_system_ip_protocol_non_existing_route_map(self):
@@ -113,13 +121,13 @@ class TestSystemIP(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['nht', 'no-resolve-via-default'])
self.cli_commit()
# Verify CLI config applied to FRR
- frrconfig = self.getFRRconfig('', end='', daemon='zebra')
+ frrconfig = self.getFRRconfig('', end='', daemon=mgmt_daemon)
self.assertIn(f'no ip nht resolve-via-default', frrconfig)
self.cli_delete(base_path + ['nht', 'no-resolve-via-default'])
self.cli_commit()
# Verify CLI config removed to FRR
- frrconfig = self.getFRRconfig('', end='', daemon='zebra')
+ frrconfig = self.getFRRconfig('', end='', daemon=mgmt_daemon)
self.assertNotIn(f'no ip nht resolve-via-default', frrconfig)
if __name__ == '__main__':
diff --git a/smoketest/scripts/cli/test_system_ipv6.py b/smoketest/scripts/cli/test_system_ipv6.py
index 0c77c1dd4..ebf620204 100755
--- a/smoketest/scripts/cli/test_system_ipv6.py
+++ b/smoketest/scripts/cli/test_system_ipv6.py
@@ -19,16 +19,21 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
-from vyos.utils.file import read_file
+from vyos.utils.system import sysctl_read
+from vyos.xml_ref import default_value
+from vyos.frrender import mgmt_daemon
+from vyos.frrender import zebra_daemon
base_path = ['system', 'ipv6']
-file_forwarding = '/proc/sys/net/ipv6/conf/all/forwarding'
-file_disable = '/proc/sys/net/ipv6/conf/all/disable_ipv6'
-file_dad = '/proc/sys/net/ipv6/conf/all/accept_dad'
-file_multipath = '/proc/sys/net/ipv6/fib_multipath_hash_policy'
-
class TestSystemIPv6(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ super(TestSystemIPv6, cls).setUpClass()
+ # ensure we can also run this test on a live system - so lets clean
+ # out the current configuration :)
+ cls.cli_delete(cls, base_path)
+
def tearDown(self):
self.cli_delete(base_path)
self.cli_commit()
@@ -36,16 +41,23 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase):
def test_system_ipv6_forwarding(self):
# Test if IPv6 forwarding can be disabled globally, default is '1'
# which means forwearding enabled
- self.assertEqual(read_file(file_forwarding), '1')
+ self.assertEqual(sysctl_read('net.ipv6.conf.all.forwarding'), '1')
self.cli_set(base_path + ['disable-forwarding'])
self.cli_commit()
+ self.assertEqual(sysctl_read('net.ipv6.conf.all.forwarding'), '0')
+ frrconfig = self.getFRRconfig('', end='', daemon=zebra_daemon)
+ self.assertIn('no ipv6 forwarding', frrconfig)
- self.assertEqual(read_file(file_forwarding), '0')
+ self.cli_delete(base_path + ['disable-forwarding'])
+ self.cli_commit()
+ self.assertEqual(sysctl_read('net.ipv6.conf.all.forwarding'), '1')
+ frrconfig = self.getFRRconfig('', end='', daemon=zebra_daemon)
+ self.assertNotIn('no ipv6 forwarding', frrconfig)
def test_system_ipv6_strict_dad(self):
# This defaults to 1
- self.assertEqual(read_file(file_dad), '1')
+ self.assertEqual(sysctl_read('net.ipv6.conf.all.accept_dad'), '1')
# Do not assign any IPv6 address on interfaces, this requires a reboot
# which can not be tested, but we can read the config file :)
@@ -53,11 +65,11 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify configuration file
- self.assertEqual(read_file(file_dad), '2')
+ self.assertEqual(sysctl_read('net.ipv6.conf.all.accept_dad'), '2')
def test_system_ipv6_multipath(self):
# This defaults to 0
- self.assertEqual(read_file(file_multipath), '0')
+ self.assertEqual(sysctl_read('net.ipv6.fib_multipath_hash_policy'), '0')
# Do not assign any IPv6 address on interfaces, this requires a reboot
# which can not be tested, but we can read the config file :)
@@ -65,26 +77,24 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify configuration file
- self.assertEqual(read_file(file_multipath), '1')
+ self.assertEqual(sysctl_read('net.ipv6.fib_multipath_hash_policy'), '1')
def test_system_ipv6_neighbor_table_size(self):
# Maximum number of entries to keep in the ARP cache, the
# default is 8192
+ cli_default = int(default_value(base_path + ['neighbor', 'table-size']))
- gc_thresh3 = '/proc/sys/net/ipv6/neigh/default/gc_thresh3'
- gc_thresh2 = '/proc/sys/net/ipv6/neigh/default/gc_thresh2'
- gc_thresh1 = '/proc/sys/net/ipv6/neigh/default/gc_thresh1'
- self.assertEqual(read_file(gc_thresh3), '8192')
- self.assertEqual(read_file(gc_thresh2), '4096')
- self.assertEqual(read_file(gc_thresh1), '1024')
+ def _verify_gc_thres(table_size):
+ self.assertEqual(sysctl_read('net.ipv6.neigh.default.gc_thresh3'), str(table_size))
+ self.assertEqual(sysctl_read('net.ipv6.neigh.default.gc_thresh2'), str(table_size // 2))
+ self.assertEqual(sysctl_read('net.ipv6.neigh.default.gc_thresh1'), str(table_size // 8))
+
+ _verify_gc_thres(cli_default)
for size in [1024, 2048, 4096, 8192, 16384, 32768]:
self.cli_set(base_path + ['neighbor', 'table-size', str(size)])
self.cli_commit()
-
- self.assertEqual(read_file(gc_thresh3), str(size))
- self.assertEqual(read_file(gc_thresh2), str(size // 2))
- self.assertEqual(read_file(gc_thresh1), str(size // 8))
+ _verify_gc_thres(size)
def test_system_ipv6_protocol_route_map(self):
protocols = ['any', 'babel', 'bgp', 'connected', 'isis',
@@ -99,7 +109,7 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify route-map properly applied to FRR
- frrconfig = self.getFRRconfig('ipv6 protocol', end='', daemon='zebra')
+ frrconfig = self.getFRRconfig('ipv6 protocol', end='', daemon=mgmt_daemon)
for protocol in protocols:
# VyOS and FRR use a different name for OSPFv3 (IPv6)
if protocol == 'ospfv3':
@@ -113,7 +123,7 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify route-map properly applied to FRR
- frrconfig = self.getFRRconfig('ipv6 protocol', end='', daemon='zebra')
+ frrconfig = self.getFRRconfig('ipv6 protocol', end='', daemon=mgmt_daemon)
self.assertNotIn(f'ipv6 protocol', frrconfig)
def test_system_ipv6_protocol_non_existing_route_map(self):
@@ -132,13 +142,13 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['nht', 'no-resolve-via-default'])
self.cli_commit()
# Verify CLI config applied to FRR
- frrconfig = self.getFRRconfig('', end='', daemon='zebra')
+ frrconfig = self.getFRRconfig('', end='', daemon=mgmt_daemon)
self.assertIn(f'no ipv6 nht resolve-via-default', frrconfig)
self.cli_delete(base_path + ['nht', 'no-resolve-via-default'])
self.cli_commit()
# Verify CLI config removed to FRR
- frrconfig = self.getFRRconfig('', end='', daemon='zebra')
+ frrconfig = self.getFRRconfig('', end='', daemon=mgmt_daemon)
self.assertNotIn(f'no ipv6 nht resolve-via-default', frrconfig)
if __name__ == '__main__':
diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py
index 2bb6c91c1..f4ed1a61f 100755
--- a/smoketest/scripts/cli/test_vrf.py
+++ b/smoketest/scripts/cli/test_vrf.py
@@ -23,6 +23,7 @@ from json import loads
from jmespath import search
from vyos.configsession import ConfigSessionError
+from vyos.frrender import mgmt_daemon
from vyos.ifconfig import Interface
from vyos.ifconfig import Section
from vyos.utils.file import read_file
@@ -112,7 +113,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
regex = f'{table}\s+{vrf}\s+#\s+{description}'
self.assertTrue(re.findall(regex, iproute2_config))
- frrconfig = self.getFRRconfig(f'vrf {vrf}')
+ frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon)
self.assertIn(f' vni {table}', frrconfig)
self.assertEqual(int(table), get_vrf_tableid(vrf))
@@ -233,7 +234,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
self.assertTrue(interface_exists(vrf))
- frrconfig = self.getFRRconfig(f'vrf {vrf}')
+ frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon)
self.assertIn(f' vni {table}', frrconfig)
self.assertIn(f' ip route {prefix} {next_hop}', frrconfig)
@@ -317,7 +318,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
# Verify route-map properly applied to FRR
for vrf in vrfs:
- frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra')
+ frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon)
self.assertIn(f'vrf {vrf}', frrconfig)
for protocol in v4_protocols:
self.assertIn(f' ip protocol {protocol} route-map route-map-{vrf}-{protocol}', frrconfig)
@@ -332,8 +333,8 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
# Verify route-map properly is removed from FRR
for vrf in vrfs:
- frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra')
- self.assertNotIn(f'vrf {vrf}', frrconfig)
+ frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon)
+ self.assertNotIn(f' ip protocol', frrconfig)
def test_vrf_ip_ipv6_protocol_non_existing_route_map(self):
table = '6100'
@@ -380,7 +381,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
# Verify route-map properly applied to FRR
for vrf in vrfs:
- frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra')
+ frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon)
self.assertIn(f'vrf {vrf}', frrconfig)
for protocol in v6_protocols:
# VyOS and FRR use a different name for OSPFv3 (IPv6)
@@ -399,8 +400,8 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
# Verify route-map properly is removed from FRR
for vrf in vrfs:
- frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra')
- self.assertNotIn(f'vrf {vrf}', frrconfig)
+ frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon)
+ self.assertNotIn(f' ipv6 protocol', frrconfig)
def test_vrf_vni_duplicates(self):
base_table = '6300'
@@ -429,7 +430,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
for vrf in vrfs:
self.assertTrue(interface_exists(vrf))
- frrconfig = self.getFRRconfig(f'vrf {vrf}')
+ frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon)
self.assertIn(f' vni {table}', frrconfig)
# Increment table ID for the next run
table = str(int(table) + 1)
@@ -451,7 +452,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
for vrf in vrfs:
self.assertTrue(interface_exists(vrf))
- frrconfig = self.getFRRconfig(f'vrf {vrf}')
+ frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon)
self.assertIn(f' vni {table}', frrconfig)
# Increment table ID for the next run
table = str(int(table) + 1)
@@ -474,7 +475,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
for vrf in vrfs:
self.assertTrue(interface_exists(vrf))
- frrconfig = self.getFRRconfig(f'vrf {vrf}')
+ frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon)
self.assertIn(f' vni {table}', frrconfig)
# Increment table ID for the next run
table = str(int(table) + 2)
@@ -494,7 +495,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
for vrf in vrfs:
self.assertTrue(interface_exists(vrf))
- frrconfig = self.getFRRconfig(f'vrf {vrf}')
+ frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon)
self.assertIn(f' vni {table}', frrconfig)
# Increment table ID for the next run
table = str(int(table) + 2)
@@ -502,7 +503,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
# Verify purple VRF/VNI
self.assertTrue(interface_exists(purple))
table = str(int(table) + 10)
- frrconfig = self.getFRRconfig(f'vrf {purple}')
+ frrconfig = self.getFRRconfig(f'vrf {purple}', daemon=mgmt_daemon)
self.assertIn(f' vni {table}', frrconfig)
# Now delete all the VNIs
@@ -517,12 +518,12 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
for vrf in vrfs:
self.assertTrue(interface_exists(vrf))
- frrconfig = self.getFRRconfig(f'vrf {vrf}')
+ frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon)
self.assertNotIn('vni', frrconfig)
# Verify purple VNI remains
self.assertTrue(interface_exists(purple))
- frrconfig = self.getFRRconfig(f'vrf {purple}')
+ frrconfig = self.getFRRconfig(f'vrf {purple}', daemon=mgmt_daemon)
self.assertIn(f' vni {table}', frrconfig)
def test_vrf_ip_ipv6_nht(self):
@@ -540,7 +541,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
# Verify route-map properly applied to FRR
for vrf in vrfs:
- frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra')
+ frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon)
self.assertIn(f'vrf {vrf}', frrconfig)
self.assertIn(f' no ip nht resolve-via-default', frrconfig)
self.assertIn(f' no ipv6 nht resolve-via-default', frrconfig)
@@ -555,7 +556,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
# Verify route-map properly is removed from FRR
for vrf in vrfs:
- frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra')
+ frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon)
self.assertNotIn(f' no ip nht resolve-via-default', frrconfig)
self.assertNotIn(f' no ipv6 nht resolve-via-default', frrconfig)
diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py
index bbbfb0385..0844d2913 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,19 +31,20 @@ 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
from vyos.utils.network import interface_exists
+from vyos.utils.process import is_systemd_service_running
from vyos.configdict import has_address_configured
from vyos.configdict import has_vrf_configured
-from vyos.configdep import set_dependents, call_dependents
+from vyos.configdep import set_dependents
+from vyos.configdep import call_dependents
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
@@ -87,10 +89,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({'frr_dict' : get_frrender_dict(conf)})
# determine which members have been removed
interfaces_removed = leaf_node_changed(conf, base + [ifname, 'member', 'interface'])
@@ -260,16 +265,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 'frr_dict' in bond and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(bond['frr_dict'])
return None
def apply(bond):
- ifname = bond['ifname']
- b = BondIf(ifname)
+ if 'frr_dict' in bond and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().apply()
+
+ b = BondIf(bond['ifname'])
if 'deleted' in bond:
- # delete interface
b.remove()
else:
b.update(bond)
@@ -281,17 +286,6 @@ def apply(bond):
raise ConfigError('Error in updating ethernet interface '
'after deleting it from bond')
- zebra_daemon = 'zebra'
- # 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(zebra_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(zebra_daemon)
-
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py
index 34ce7bc47..5024e6982 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,15 +34,15 @@ 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.utils.process import is_systemd_service_running
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
@@ -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({'frr_dict' : get_frrender_dict(conf)})
+
return ethernet
def verify_speed_duplex(ethernet: dict, ethtool: Ethtool):
@@ -318,42 +322,25 @@ 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 'frr_dict' in ethernet and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(ethernet['frr_dict'])
return None
def apply(ethernet):
- ifname = ethernet['ifname']
-
- e = EthernetIf(ifname)
+ if 'frr_dict' in ethernet and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().apply()
+ e = EthernetIf(ethernet['ifname'])
if 'deleted' in ethernet:
- # delete interface
e.remove()
else:
e.update(ethernet)
-
- zebra_daemon = 'zebra'
- # 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(zebra_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(zebra_daemon)
+ return None
if __name__ == '__main__':
try:
c = get_config()
verify(c)
generate(c)
-
apply(c)
except ConfigError as e:
print(e)
diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py
index a5963e72c..5e71a612d 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,16 +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.utils.process import is_systemd_service_running
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
-
airbag.enable()
-
def community_action_compatibility(actions: dict) -> bool:
"""
Check compatibility of values in community and large community sections
@@ -87,31 +87,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,72 +242,36 @@ 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!')
+ # 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!')
return None
-def generate(policy):
- if not policy:
- return None
- policy['new_frr_config'] = render_to_string('frr/policy.frr.j2', policy)
+def generate(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(config_dict)
return None
-
-def apply(policy):
- bgp_daemon = 'bgpd'
- zebra_daemon = 'zebra'
-
- # 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(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(bgp_daemon)
-
- # The route-map used for the FIB (zebra) is part of the zebra daemon
- frr_cfg.load_configuration(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(zebra_daemon)
-
+def apply(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ 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 90b6e4a31..48b7ae734 100755
--- a/src/conf_mode/protocols_babel.py
+++ b/src/conf_mode/protocols_babel.py
@@ -17,15 +17,14 @@
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.utils.process import is_systemd_service_running
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
@@ -34,46 +33,16 @@ def get_config(config=None):
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)
+ return get_frrender_dict(conf)
- # 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)
-
- # 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,32 +89,14 @@ 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)
+def generate(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(config_dict)
return None
-def apply(babel):
- babel_daemon = 'babeld'
-
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
-
- frr_cfg.load_configuration(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(babel_daemon)
-
+def apply(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py
index 1361bb1a9..2e7d40676 100755
--- a/src/conf_mode/protocols_bfd.py
+++ b/src/conf_mode/protocols_bfd.py
@@ -15,12 +15,14 @@
# 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.utils.process import is_systemd_service_running
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
@@ -29,22 +31,14 @@ def get_config(config=None):
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 get_frrender_dict(conf)
- return bfd
-
-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,22 +77,13 @@ 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):
- bfd_daemon = 'bfdd'
-
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
- frr_cfg.load_configuration(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(bfd_daemon)
+def generate(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(config_dict)
+def apply(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index 22f020099..60f3f2ad0 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -19,21 +19,20 @@ 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 is_systemd_service_running
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()
@@ -43,68 +42,7 @@ def get_config(config=None):
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, argv)
def verify_vrf_as_import(search_vrf_name: str, afi_name: str, vrfs_config: dict) -> bool:
"""
@@ -237,13 +175,24 @@ def verify_afi(peer_config, bgp_config):
if tmp: return True
return False
-def verify(bgp):
+def verify(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'bgp'):
+ return None
+
+ vrf = None
+ if 'vrf_context' in config_dict:
+ vrf = config_dict['vrf_context']
+
+ # 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 'deleted' in bgp:
- if 'vrf' in bgp:
+ if vrf:
# Cannot delete vrf if it exists in import vrf list in other vrfs
for tmp_afi in ['ipv4_unicast', 'ipv6_unicast']:
- if verify_vrf_as_import(bgp['vrf'], tmp_afi, bgp['dependent_vrfs']):
- raise ConfigError(f'Cannot delete VRF instance "{bgp["vrf"]}", ' \
+ if verify_vrf_as_import(vrf, tmp_afi, bgp['dependent_vrfs']):
+ raise ConfigError(f'Cannot delete VRF instance "{vrf}", ' \
'unconfigure "import vrf" commands!')
else:
# We are running in the default VRF context, thus we can not delete
@@ -252,8 +201,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!')
@@ -281,9 +231,8 @@ def verify(bgp):
for interface in bgp['interface']:
error_msg = f'Interface "{interface}" belongs to different VRF instance'
tmp = get_interface_vrf(interface)
- if 'vrf' in bgp:
- if bgp['vrf'] != tmp:
- vrf = bgp['vrf']
+ if vrf:
+ if vrf != tmp:
raise ConfigError(f'{error_msg} "{vrf}"!')
elif tmp != 'default':
raise ConfigError(f'{error_msg} "{tmp}"!')
@@ -384,10 +333,8 @@ def verify(bgp):
# Only checks for ipv4 and ipv6 neighbors
# Check if neighbor address is assigned as system interface address
- vrf = None
vrf_error_msg = f' in default VRF!'
- if 'vrf' in bgp:
- vrf = bgp['vrf']
+ if vrf:
vrf_error_msg = f' in VRF "{vrf}"!'
if is_ip(peer) and is_addr_assigned(peer, vrf):
@@ -529,7 +476,7 @@ def verify(bgp):
f'{afi} administrative distance {key}!')
if afi in ['ipv4_unicast', 'ipv6_unicast']:
- vrf_name = bgp['vrf'] if dict_search('vrf', bgp) else 'default'
+ vrf_name = vrf if vrf else 'default'
# Verify if currant VRF contains rd and route-target options
# and does not exist in import list in other VRFs
if dict_search(f'rd.vpn.export', afi_config):
@@ -602,46 +549,14 @@ 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)
+def generate(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(config_dict)
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))
-
- bgp_daemon = 'bgpd'
-
- # 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(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(bgp_daemon)
-
+def apply(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_eigrp.py b/src/conf_mode/protocols_eigrp.py
index c13e52a3d..8f49bb151 100755
--- a/src/conf_mode/protocols_eigrp.py
+++ b/src/conf_mode/protocols_eigrp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-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
@@ -18,94 +18,49 @@ 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.utils.process import is_systemd_service_running
+from vyos.frrender import FRRender
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
-
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- vrf = None
- if len(argv) > 1:
- vrf = argv[1]
-
- base_path = ['protocols', 'eigrp']
-
- # 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)
+ return get_frrender_dict(conf, argv)
- # 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)
+def verify(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'eigrp'):
+ return None
- return eigrp
+ vrf = None
+ if 'vrf_context' in config_dict:
+ vrf = config_dict['vrf_context']
-def verify(eigrp):
- if not eigrp or 'deleted' in eigrp:
- return
+ # eqivalent of the C foo ? 'a' : 'b' statement
+ 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!')
- 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)
+ if vrf:
+ verify_vrf({'vrf': vrf})
-def apply(eigrp):
- eigrp_daemon = 'eigrpd'
-
- # 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(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(eigrp_daemon)
+def generate(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(config_dict)
+ return None
+def apply(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py
index ba2f3cf0d..1e5f0d6e8 100755
--- a/src/conf_mode/protocols_isis.py
+++ b/src/conf_mode/protocols_isis.py
@@ -18,16 +18,16 @@ 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.utils.process import is_systemd_service_running
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
@@ -37,54 +37,21 @@ def get_config(config=None):
else:
conf = Config()
- vrf = None
- if len(argv) > 1:
- vrf = argv[1]
+ return get_frrender_dict(conf, argv)
+
+def verify(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'isis'):
+ return None
- base_path = ['protocols', 'isis']
+ vrf = None
+ if 'vrf_context' in config_dict:
+ vrf = config_dict['vrf_context']
# 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:
+ 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 'net' not in isis:
@@ -114,12 +81,11 @@ def verify(isis):
f'Recommended area lsp-mtu {recom_area_mtu} or less ' \
'(calculated on MTU size).')
- if 'vrf' in isis:
+ if vrf:
# If interface specific options are set, we must ensure that the
# 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.
- vrf = isis['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}"!')
@@ -266,39 +232,14 @@ 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)
+def generate(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(config_dict)
return None
-def apply(isis):
- isis_daemon = 'isisd'
-
- # 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(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(isis_daemon)
-
+def apply(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py
index ad164db9f..e8097b7ff 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,32 @@ 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.process import is_systemd_service_running
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'
-
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,26 +67,19 @@ 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
-
- mpls['frr_ldpd_config'] = render_to_string('frr/ldpd.frr.j2', mpls)
+def generate(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(config_dict)
return None
-def apply(mpls):
- ldpd_damon = 'ldpd'
-
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
+def apply(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().apply()
- frr_cfg.load_configuration(ldpd_damon)
- 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(ldpd_damon)
+ 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 8e8c50c06..41c5d9544 100644
--- a/src/conf_mode/protocols_openfabric.py
+++ b/src/conf_mode/protocols_openfabric.py
@@ -18,13 +18,13 @@ 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.utils.process import is_systemd_service_running
+from vyos.frrender import FRRender
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
-
airbag.enable()
def get_config(config=None):
@@ -33,32 +33,14 @@ def get_config(config=None):
else:
conf = Config()
- base_path = ['protocols', 'openfabric']
-
- 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)
+ return get_frrender_dict(conf)
- 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,31 +89,14 @@ 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)
+def generate(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(config_dict)
return None
-def apply(openfabric):
- openfabric_daemon = 'fabricd'
-
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
-
- frr_cfg.load_configuration(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(openfabric_daemon)
-
+def apply(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index 7347c4faa..f2c95a63c 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -18,18 +18,17 @@ 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.utils.process import is_systemd_service_running
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
@@ -39,85 +38,19 @@ def get_config(config=None):
else:
conf = Config()
- vrf = None
- if len(argv) > 1:
- vrf = argv[1]
-
- base_path = ['protocols', 'ospf']
-
- # 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
+ return get_frrender_dict(conf, argv)
- # 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)
+def verify(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'ospf'):
+ return None
- return ospf
+ vrf = None
+ if 'vrf_context' in config_dict:
+ vrf = config_dict['vrf_context']
-def verify(ospf):
- if not ospf:
- return None
+ # eqivalent of the C foo ? 'a' : 'b' statement
+ 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 +97,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,39 +176,14 @@ 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)
+def generate(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(config_dict)
return None
-def apply(ospf):
- ospf_daemon = 'ospfd'
-
- # 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(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(ospf_daemon)
-
+def apply(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py
index 60c2a9b16..ac189c378 100755
--- a/src/conf_mode/protocols_ospfv3.py
+++ b/src/conf_mode/protocols_ospfv3.py
@@ -18,18 +18,17 @@ 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.utils.process import is_systemd_service_running
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
@@ -39,75 +38,19 @@ def get_config(config=None):
else:
conf = Config()
- vrf = None
- if len(argv) > 1:
- vrf = argv[1]
+ return get_frrender_dict(conf, argv)
+
+def verify(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'ospfv3'):
+ return None
- base_path = ['protocols', 'ospfv3']
+ vrf = None
+ if 'vrf_context' in config_dict:
+ vrf = config_dict['vrf_context']
# 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,47 +80,21 @@ 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)
+def generate(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(config_dict)
return None
-def apply(ospfv3):
- ospf6_daemon = 'ospf6d'
-
- # 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(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(ospf6_daemon)
-
+def apply(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py
index 79294a1f0..477895b0b 100755
--- a/src/conf_mode/protocols_pim.py
+++ b/src/conf_mode/protocols_pim.py
@@ -22,72 +22,33 @@ from signal import SIGTERM
from sys import exit
from vyos.config import Config
-from vyos.config import config_dict_merge
-from vyos.configdict import node_changed
+from vyos.configdict import get_frrender_dict
from vyos.configverify import verify_interface_exists
+from vyos.configverify import has_frr_protocol_in_dict
+from vyos.frrender import FRRender
+from vyos.frrender import pim_daemon
+from vyos.utils.process import is_systemd_service_running
from vyos.utils.process import process_named_running
from vyos.utils.process import call
-from vyos.template import render_to_string
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
-RESERVED_MC_NET = '224.0.0.0/24'
-
-
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- base = ['protocols', 'pim']
-
- pim = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True, no_tag_node_value_mangle=True)
-
- # We can not run both IGMP proxy and PIM at the same time - get IGMP
- # proxy status
- if conf.exists(['protocols', 'igmp-proxy']):
- pim.update({'igmp_proxy_enabled' : {}})
-
- # FRR has VRF support for different routing daemons. As interfaces belong
- # to VRFs - or the global VRF, we need to check for changed interfaces so
- # that they will be properly rendered for the FRR config. Also this eases
- # removal of interfaces from the running configuration.
- interfaces_removed = node_changed(conf, base + ['interface'])
- if interfaces_removed:
- pim['interface_removed'] = list(interfaces_removed)
-
- # Bail out early if configuration tree does no longer exist. this must
- # be done after retrieving the list of interfaces to be removed.
- if not conf.exists(base):
- pim.update({'deleted' : ''})
- return pim
-
- # We have gathered the dict representation of the CLI, but there are default
- # options which we need to update into the dictionary retrived.
- default_values = conf.get_config_defaults(**pim.kwargs, recursive=True)
-
- # We have to cleanup the default dict, as default values could enable features
- # which are not explicitly enabled on the CLI. Example: default-information
- # originate comes with a default metric-type of 2, which will enable the
- # entire default-information originate tree, even when not set via CLI so we
- # need to check this first and probably drop that key.
- for interface in pim.get('interface', []):
- # We need to reload the defaults on every pass b/c of
- # hello-multiplier dependency on dead-interval
- # If hello-multiplier is set, we need to remove the default from
- # dead-interval.
- if 'igmp' not in pim['interface'][interface]:
- del default_values['interface'][interface]['igmp']
-
- pim = config_dict_merge(default_values, pim)
- return pim
-
-def verify(pim):
- if not pim or 'deleted' in pim:
+ return get_frrender_dict(conf)
+
+def verify(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'pim'):
+ return None
+
+ pim = config_dict['pim']
+
+ if 'deleted' in pim:
return None
if 'igmp_proxy_enabled' in pim:
@@ -96,6 +57,7 @@ def verify(pim):
if 'interface' not in pim:
raise ConfigError('PIM require defined interfaces!')
+ RESERVED_MC_NET = '224.0.0.0/24'
for interface, interface_config in pim['interface'].items():
verify_interface_exists(pim, interface)
@@ -124,41 +86,26 @@ def verify(pim):
raise ConfigError(f'{pim_base_error} must be unique!')
unique.append(gr_addr)
-def generate(pim):
- if not pim or 'deleted' in pim:
- return None
- pim['frr_pimd_config'] = render_to_string('frr/pimd.frr.j2', pim)
+def generate(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(config_dict)
return None
-def apply(pim):
- pim_daemon = 'pimd'
- pim_pid = process_named_running(pim_daemon)
-
- if not pim or 'deleted' in pim:
- if 'deleted' in pim:
- os.kill(int(pim_pid), SIGTERM)
+def apply(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'pim'):
+ return None
+ pim_pid = process_named_running(pim_daemon)
+ pim = config_dict['pim']
+ if 'deleted' in pim:
+ os.kill(int(pim_pid), SIGTERM)
return None
if not pim_pid:
call('/usr/lib/frr/pimd -d -F traditional --daemon -A 127.0.0.1')
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
-
- frr_cfg.load_configuration(pim_daemon)
- frr_cfg.modify_section(f'^ip pim')
- frr_cfg.modify_section(f'^ip igmp')
-
- for key in ['interface', 'interface_removed']:
- if key not in pim:
- continue
- for interface in pim[key]:
- frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True)
-
- if 'frr_pimd_config' in pim:
- frr_cfg.add_before(frr.default_add_before, pim['frr_pimd_config'])
- frr_cfg.commit_configuration(pim_daemon)
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_pim6.py b/src/conf_mode/protocols_pim6.py
index 581ffe238..3a9b876cc 100755
--- a/src/conf_mode/protocols_pim6.py
+++ b/src/conf_mode/protocols_pim6.py
@@ -19,12 +19,12 @@ from ipaddress import IPv6Network
from sys import exit
from vyos.config import Config
-from vyos.config import config_dict_merge
-from vyos.configdict import node_changed
+from vyos.configdict import get_frrender_dict
+from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_interface_exists
-from vyos.template import render_to_string
+from vyos.utils.process import is_systemd_service_running
+from vyos.frrender import FRRender
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
@@ -33,34 +33,15 @@ def get_config(config=None):
conf = config
else:
conf = Config()
- base = ['protocols', 'pim6']
- pim6 = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=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:
- pim6['interface_removed'] = list(interfaces_removed)
+def verify(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'pim6'):
+ return None
- # Bail out early if configuration tree does no longer exist. this must
- # be done after retrieving the list of interfaces to be removed.
- if not conf.exists(base):
- pim6.update({'deleted' : ''})
- return pim6
-
- # We have gathered the dict representation of the CLI, but there are default
- # options which we need to update into the dictionary retrived.
- default_values = conf.get_config_defaults(**pim6.kwargs, recursive=True)
-
- pim6 = config_dict_merge(default_values, pim6)
- return pim6
-
-def verify(pim6):
- if not pim6 or 'deleted' in pim6:
- return
+ pim6 = config_dict['pim6']
+ if 'deleted' in pim6:
+ return None
for interface, interface_config in pim6.get('interface', {}).items():
verify_interface_exists(pim6, interface)
@@ -94,32 +75,14 @@ def verify(pim6):
raise ConfigError(f'{pim_base_error} must be unique!')
unique.append(gr_addr)
-def generate(pim6):
- if not pim6 or 'deleted' in pim6:
- return
- pim6['new_frr_config'] = render_to_string('frr/pim6d.frr.j2', pim6)
+def generate(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(config_dict)
return None
-def apply(pim6):
- if pim6 is None:
- return
-
- pim6_daemon = 'pim6d'
-
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
-
- frr_cfg.load_configuration(pim6_daemon)
-
- for key in ['interface', 'interface_removed']:
- if key not in pim6:
- continue
- for interface in pim6[key]:
- frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True)
-
- if 'new_frr_config' in pim6:
- frr_cfg.add_before(frr.default_add_before, pim6['new_frr_config'])
- frr_cfg.commit_configuration(pim6_daemon)
+def apply(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py
index 9afac544d..39743f965 100755
--- a/src/conf_mode/protocols_rip.py
+++ b/src/conf_mode/protocols_rip.py
@@ -17,15 +17,15 @@
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.utils.process import is_systemd_service_running
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
@@ -34,41 +34,16 @@ def get_config(config=None):
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)
+ return get_frrender_dict(conf)
- # 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:
+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,39 +68,14 @@ 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)
+def generate(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(config_dict)
return None
-def apply(rip):
- rip_daemon = 'ripd'
- zebra_daemon = 'zebra'
-
- # 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(zebra_daemon)
- frr_cfg.modify_section('^ip protocol rip route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)')
- frr_cfg.commit_configuration(zebra_daemon)
-
- frr_cfg.load_configuration(rip_daemon)
- 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(rip_daemon)
-
+def apply(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_ripng.py b/src/conf_mode/protocols_ripng.py
index 23416ff96..14f038444 100755
--- a/src/conf_mode/protocols_ripng.py
+++ b/src/conf_mode/protocols_ripng.py
@@ -17,14 +17,15 @@
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.utils.process import is_systemd_service_running
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
@@ -33,32 +34,16 @@ def get_config(config=None):
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
+ return get_frrender_dict(conf)
- # 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:
+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,34 +68,14 @@ 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:
- ripng['new_frr_config'] = ''
- return None
-
- ripng['new_frr_config'] = render_to_string('frr/ripngd.frr.j2', ripng)
+def generate(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(config_dict)
return None
-def apply(ripng):
- ripng_daemon = 'ripngd'
- zebra_daemon = 'zebra'
-
- # 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(zebra_daemon)
- frr_cfg.modify_section('^ipv6 protocol ripng route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)')
- frr_cfg.commit_configuration(zebra_daemon)
-
- frr_cfg.load_configuration(ripng_daemon)
- 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(ripng_daemon)
-
+def apply(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py
index a59ecf3e4..5ad656586 100755
--- a/src/conf_mode/protocols_rpki.py
+++ b/src/conf_mode/protocols_rpki.py
@@ -20,13 +20,15 @@ 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.utils.process import is_systemd_service_running
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
@@ -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,21 +95,13 @@ 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)
-
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(config_dict)
return None
-def apply(rpki):
- bgp_daemon = 'bgpd'
-
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
- frr_cfg.load_configuration(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(bgp_daemon)
+def apply(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ 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 b36c2ca11..99cf87556 100755
--- a/src/conf_mode/protocols_segment-routing.py
+++ b/src/conf_mode/protocols_segment-routing.py
@@ -17,12 +17,15 @@
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.process import is_systemd_service_running
from vyos.utils.system import sysctl_write
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
@@ -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,47 +54,43 @@ def verify(sr):
raise ConfigError('SRv6 should be enabled on at least one interface!')
return None
-def generate(sr):
- if not sr:
- return None
-
- sr['new_frr_config'] = render_to_string('frr/zebra.segment_routing.frr.j2', sr)
+def generate(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(config_dict)
return None
-def apply(sr):
- zebra_daemon = 'zebra'
+def apply(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'segment_routing'):
+ return None
- 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')
+ sr = config_dict['segment_routing']
+
+ 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(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(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')
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().apply()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py
index 430cc69d4..9d02db6dd 100755
--- a/src/conf_mode/protocols_static.py
+++ b/src/conf_mode/protocols_static.py
@@ -14,19 +14,19 @@
# 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.utils.process import is_systemd_service_running
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()
@@ -38,36 +38,20 @@ def get_config(config=None):
else:
conf = Config()
+ return get_frrender_dict(conf, argv)
+
+def verify(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'static'):
+ return None
+
vrf = None
- if len(argv) > 1:
- vrf = argv[1]
+ if 'vrf_context' in config_dict:
+ vrf = config_dict['vrf_context']
- 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,35 +74,34 @@ 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):
+ if not has_frr_protocol_in_dict(config_dict, 'static'):
return None
- # 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)
- return None
-
-def apply(static):
- static_daemon = 'staticd'
+ vrf = None
+ if 'vrf_context' in config_dict:
+ vrf = config_dict['vrf_context']
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
- frr_cfg.load_configuration(static_daemon)
+ # eqivalent of the C foo ? 'a' : 'b' statement
+ static = vrf and config_dict['vrf']['name'][vrf]['protocols']['static'] or config_dict['static']
- 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 .*')
+ # Put routing table names in /etc/iproute2/rt_tables
+ render(config_file, 'iproute2/static.conf.j2', static)
- if 'new_frr_config' in static:
- frr_cfg.add_before(frr.default_add_before, static['new_frr_config'])
- frr_cfg.commit_configuration(static_daemon)
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(config_dict)
+ return None
+def apply(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ 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 c8894fd41..000000000
--- a/src/conf_mode/protocols_static_multicast.py
+++ /dev/null
@@ -1,135 +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
- static_daemon = 'staticd'
-
- frr_cfg = frr.FRRConfig()
- frr_cfg.load_configuration(static_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(static_daemon)
-
- 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 c9c0ed9a0..1174b1238 100755
--- a/src/conf_mode/service_snmp.py
+++ b/src/conf_mode/service_snmp.py
@@ -260,15 +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 = ['zebra', 'bgpd', 'ospf6d', 'ospfd', 'ripd', 'isisd', 'ldpd']
- 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/system_ip.py b/src/conf_mode/system_ip.py
index c8a91fd2f..86843eb78 100755
--- a/src/conf_mode/system_ip.py
+++ b/src/conf_mode/system_ip.py
@@ -17,17 +17,17 @@
from sys import exit
from vyos.config import Config
-from vyos.configdict import dict_merge
+from vyos.configdep import set_dependents
+from vyos.configdep import call_dependents
+from vyos.configdict import get_frrender_dict
+from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_route_map
-from vyos.template import render_to_string
+from vyos.frrender import FRRender
from vyos.utils.dict import dict_search
-from vyos.utils.file import write_file
from vyos.utils.process import is_systemd_service_active
+from vyos.utils.process import is_systemd_service_running
from vyos.utils.system import sysctl_write
-from vyos.configdep import set_dependents
-from vyos.configdep import call_dependents
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
@@ -36,42 +36,36 @@ def get_config(config=None):
conf = config
else:
conf = Config()
- base = ['system', 'ip']
-
- opt = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True,
- with_recursive_defaults=True)
-
- # When working with FRR we need to know the corresponding address-family
- opt['afi'] = 'ip'
-
- # We also need the route-map information from the config
- #
- # XXX: one MUST always call this without the key_mangling() option! See
- # vyos.configverify.verify_common_route_maps() for more information.
- tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'],
- get_first_key=True)}}
- # Merge policy dict into "regular" config dict
- opt = dict_merge(tmp, opt)
# If IPv4 ARP table size is set here and also manually in sysctl, the more
# fine grained value from sysctl must win
set_dependents('sysctl', conf)
+ return get_frrender_dict(conf)
+
+def verify(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'ip'):
+ return None
- return opt
+ opt = config_dict['ip']
+ opt['policy'] = config_dict['policy']
-def verify(opt):
if 'protocol' in opt:
for protocol, protocol_options in opt['protocol'].items():
if 'route_map' in protocol_options:
verify_route_map(protocol_options['route_map'], opt)
return
-def generate(opt):
- opt['frr_zebra_config'] = render_to_string('frr/zebra.route-map.frr.j2', opt)
- return
+def generate(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(config_dict)
+ return None
+
+def apply(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'ip'):
+
+ return None
+ opt = config_dict['ip']
-def apply(opt):
# Apply ARP threshold values
# table_size has a default value - thus the key always exists
size = int(dict_search('arp.table_size', opt))
@@ -82,11 +76,6 @@ def apply(opt):
# Minimum number of stored records is indicated which is not cleared
sysctl_write('net.ipv4.neigh.default.gc_thresh1', size // 8)
- # enable/disable IPv4 forwarding
- tmp = dict_search('disable_forwarding', opt)
- value = '0' if (tmp != None) else '1'
- write_file('/proc/sys/net/ipv4/conf/all/forwarding', value)
-
# configure multipath
tmp = dict_search('multipath.ignore_unreachable_nexthops', opt)
value = '1' if (tmp != None) else '0'
@@ -121,19 +110,11 @@ def apply(opt):
# running when this script is called first. Skip this part and wait for initial
# commit of the configuration to trigger this statement
if is_systemd_service_active('frr.service'):
- zebra_daemon = 'zebra'
- # 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(zebra_daemon)
- frr_cfg.modify_section(r'no ip nht resolve-via-default')
- frr_cfg.modify_section(r'ip protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)')
- if 'frr_zebra_config' in opt:
- frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config'])
- frr_cfg.commit_configuration(zebra_daemon)
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().apply()
call_dependents()
+ return None
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/system_ipv6.py b/src/conf_mode/system_ipv6.py
index a2442d009..593b8f7f3 100755
--- a/src/conf_mode/system_ipv6.py
+++ b/src/conf_mode/system_ipv6.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2023 VyOS maintainers and contributors
+# Copyright (C) 2019-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
@@ -18,17 +18,18 @@ import os
from sys import exit
from vyos.config import Config
-from vyos.configdict import dict_merge
+from vyos.configdep import set_dependents
+from vyos.configdep import call_dependents
+from vyos.configdict import get_frrender_dict
+from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_route_map
-from vyos.template import render_to_string
+from vyos.frrender import FRRender
from vyos.utils.dict import dict_search
from vyos.utils.file import write_file
from vyos.utils.process import is_systemd_service_active
+from vyos.utils.process import is_systemd_service_running
from vyos.utils.system import sysctl_write
-from vyos.configdep import set_dependents
-from vyos.configdep import call_dependents
from vyos import ConfigError
-from vyos import frr
from vyos import airbag
airbag.enable()
@@ -37,42 +38,35 @@ def get_config(config=None):
conf = config
else:
conf = Config()
- base = ['system', 'ipv6']
-
- opt = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True,
- with_recursive_defaults=True)
-
- # When working with FRR we need to know the corresponding address-family
- opt['afi'] = 'ipv6'
-
- # We also need the route-map information from the config
- #
- # XXX: one MUST always call this without the key_mangling() option! See
- # vyos.configverify.verify_common_route_maps() for more information.
- tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'],
- get_first_key=True)}}
- # Merge policy dict into "regular" config dict
- opt = dict_merge(tmp, opt)
# If IPv6 neighbor table size is set here and also manually in sysctl, the more
# fine grained value from sysctl must win
set_dependents('sysctl', conf)
+ return get_frrender_dict(conf)
+
+def verify(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'ipv6'):
+ return None
- return opt
+ opt = config_dict['ipv6']
+ opt['policy'] = config_dict['policy']
-def verify(opt):
if 'protocol' in opt:
for protocol, protocol_options in opt['protocol'].items():
if 'route_map' in protocol_options:
verify_route_map(protocol_options['route_map'], opt)
return
-def generate(opt):
- opt['frr_zebra_config'] = render_to_string('frr/zebra.route-map.frr.j2', opt)
- return
+def generate(config_dict):
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(config_dict)
+ return None
+
+def apply(config_dict):
+ if not has_frr_protocol_in_dict(config_dict, 'ipv6'):
+ return None
+ opt = config_dict['ipv6']
-def apply(opt):
# configure multipath
tmp = dict_search('multipath.layer4_hashing', opt)
value = '1' if (tmp != None) else '0'
@@ -88,11 +82,6 @@ def apply(opt):
# Minimum number of stored records is indicated which is not cleared
sysctl_write('net.ipv6.neigh.default.gc_thresh1', size // 8)
- # enable/disable IPv6 forwarding
- tmp = dict_search('disable_forwarding', opt)
- value = '0' if (tmp != None) else '1'
- write_file('/proc/sys/net/ipv6/conf/all/forwarding', value)
-
# configure IPv6 strict-dad
tmp = dict_search('strict_dad', opt)
value = '2' if (tmp != None) else '1'
@@ -105,19 +94,11 @@ def apply(opt):
# running when this script is called first. Skip this part and wait for initial
# commit of the configuration to trigger this statement
if is_systemd_service_active('frr.service'):
- zebra_daemon = 'zebra'
- # 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(zebra_daemon)
- frr_cfg.modify_section(r'no ipv6 nht resolve-via-default')
- frr_cfg.modify_section(r'ipv6 protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)')
- if 'frr_zebra_config' in opt:
- frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config'])
- frr_cfg.commit_configuration(zebra_daemon)
+ if config_dict and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().apply()
call_dependents()
+ return None
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/system_login.py b/src/conf_mode/system_login.py
index 439fa645b..d3a969d9b 100755
--- a/src/conf_mode/system_login.py
+++ b/src/conf_mode/system_login.py
@@ -58,20 +58,21 @@ MAX_RADIUS_TIMEOUT: int = 50
MAX_RADIUS_COUNT: int = 8
# Maximum number of supported TACACS servers
MAX_TACACS_COUNT: int = 8
-
+# Minimum USER id for TACACS users
+MIN_TACACS_UID = 900
# List of local user accounts that must be preserved
SYSTEM_USER_SKIP_LIST: list = ['radius_user', 'radius_priv_user', 'tacacs0', 'tacacs1',
'tacacs2', 'tacacs3', 'tacacs4', 'tacacs5', 'tacacs6',
'tacacs7', 'tacacs8', 'tacacs9', 'tacacs10',' tacacs11',
'tacacs12', 'tacacs13', 'tacacs14', 'tacacs15']
-def get_local_users():
+def get_local_users(min_uid=MIN_USER_UID, max_uid=MAX_USER_UID):
"""Return list of dynamically allocated users (see Debian Policy Manual)"""
local_users = []
for s_user in getpwall():
- if getpwnam(s_user.pw_name).pw_uid < MIN_USER_UID:
+ if getpwnam(s_user.pw_name).pw_uid < min_uid:
continue
- if getpwnam(s_user.pw_name).pw_uid > MAX_USER_UID:
+ if getpwnam(s_user.pw_name).pw_uid > max_uid:
continue
if s_user.pw_name in SYSTEM_USER_SKIP_LIST:
continue
@@ -119,6 +120,12 @@ def get_config(config=None):
rm_users = [tmp for tmp in all_users if tmp not in cli_users]
if rm_users: login.update({'rm_users' : rm_users})
+ # Build TACACS user mapping
+ if 'tacacs' in login:
+ login['exclude_users'] = get_local_users(min_uid=0,
+ max_uid=MIN_TACACS_UID) + cli_users
+ login['tacacs_min_uid'] = MIN_TACACS_UID
+
return login
def verify(login):
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index 72b178c89..6533f493f 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -19,23 +19,23 @@ from jmespath import search
from json import loads
from vyos.config import Config
-from vyos.configdict import dict_merge
+from vyos.configdict import get_frrender_dict
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
from vyos.utils.network import interface_exists
from vyos.utils.process import call
from vyos.utils.process import cmd
+from vyos.utils.process import is_systemd_service_running
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()
@@ -132,15 +132,9 @@ def get_config(config=None):
if 'name' in vrf:
vrf['conntrack'] = conntrack_required(conf)
- # We also need the route-map information from the config
- #
- # XXX: one MUST always call this without the key_mangling() option! See
- # vyos.configverify.verify_common_route_maps() for more information.
- tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'],
- get_first_key=True)}}
-
- # Merge policy dict into "regular" config dict
- vrf = dict_merge(tmp, vrf)
+ # 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({'frr_dict' : get_frrender_dict(conf)})
return vrf
def verify(vrf):
@@ -158,6 +152,7 @@ def verify(vrf):
reserved_names = ["add", "all", "broadcast", "default", "delete", "dev",
"get", "inet", "mtu", "link", "type", "vrf"]
table_ids = []
+ vnis = []
for name, vrf_config in vrf['name'].items():
# Reserved VRF names
if name in reserved_names:
@@ -178,17 +173,24 @@ def verify(vrf):
raise ConfigError(f'VRF "{name}" table id is not unique!')
table_ids.append(vrf_config['table'])
+ # VRF VNIs must be unique on the system
+ if 'vni' in vrf_config:
+ vni = vrf_config['vni']
+ if vni in vnis:
+ raise ConfigError(f'VRF "{name}" VNI "{vni}" is not unique!')
+ vnis.append(vni)
+
tmp = dict_search('ip.protocol', vrf_config)
if tmp != None:
for protocol, protocol_options in tmp.items():
if 'route_map' in protocol_options:
- verify_route_map(protocol_options['route_map'], vrf)
+ verify_route_map(protocol_options['route_map'], vrf['frr_dict'])
tmp = dict_search('ipv6.protocol', vrf_config)
if tmp != None:
for protocol, protocol_options in tmp.items():
if 'route_map' in protocol_options:
- verify_route_map(protocol_options['route_map'], vrf)
+ verify_route_map(protocol_options['route_map'], vrf['frr_dict'])
return None
@@ -196,8 +198,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 'frr_dict' in vrf and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().generate(vrf['frr_dict'])
return None
@@ -339,17 +342,8 @@ def apply(vrf):
if has_rule(afi, 2000, 'l3mdev'):
call(f'ip {afi} rule del pref 2000 l3mdev unreachable')
- # Apply FRR filters
- zebra_daemon = 'zebra'
- # 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(zebra_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(zebra_daemon)
+ if 'frr_dict' in vrf and not is_systemd_service_running('vyos-configd.service'):
+ FRRender().apply()
return None
diff --git a/src/helpers/latest-image-url.py b/src/helpers/latest-image-url.py
new file mode 100755
index 000000000..ea201ef7c
--- /dev/null
+++ b/src/helpers/latest-image-url.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python3
+
+import sys
+
+from vyos.configquery import ConfigTreeQuery
+from vyos.version import get_remote_version
+
+
+if __name__ == '__main__':
+ image_path = ''
+
+ config = ConfigTreeQuery()
+ if config.exists('system update-check url'):
+ configured_url_version = config.value('system update-check url')
+ remote_url_list = get_remote_version(configured_url_version)
+ if remote_url_list:
+ image_path = remote_url_list[0].get('url')
+ else:
+ sys.exit(1)
+
+ print(image_path)
diff --git a/src/init/vyos-router b/src/init/vyos-router
index e2e964656..00136309b 100755
--- a/src/init/vyos-router
+++ b/src/init/vyos-router
@@ -474,9 +474,10 @@ start ()
# enable some debugging before loading the configuration
if grep -q vyos-debug /proc/cmdline; then
log_action_begin_msg "Enable runtime debugging options"
+ FRR_DEBUG=$(python3 -c "from vyos.defaults import frr_debug_enable; print(frr_debug_enable)")
+ touch $FRR_DEBUG
touch /tmp/vyos.container.debug
touch /tmp/vyos.ifconfig.debug
- touch /tmp/vyos.frr.debug
touch /tmp/vyos.container.debug
touch /tmp/vyos.smoketest.debug
fi
diff --git a/src/migration-scripts/dns-dynamic/1-to-2 b/src/migration-scripts/dns-dynamic/1-to-2
index 5dca9e32f..7f4938147 100644
--- a/src/migration-scripts/dns-dynamic/1-to-2
+++ b/src/migration-scripts/dns-dynamic/1-to-2
@@ -20,6 +20,10 @@
# - migrate "service dns dynamic address <interface> service <service> protocol dnsexit"
# to "service dns dynamic address <interface> service <service> protocol dnsexit2"
+# T6950:
+# - add if statement to prevent processing of "service dns dynamic address" options if they don't exist
+# due to the fact they are no longer valid syntax
+
from vyos.configtree import ConfigTree
base_path = ['service', 'dns', 'dynamic']
@@ -36,16 +40,19 @@ def migrate(config: ConfigTree) -> None:
if config.exists(timeout_path):
config.rename(timeout_path, 'interval')
- # Remove "service dns dynamic address <interface> web-options ..." when <interface> != "web"
- for address in config.list_nodes(address_path):
- if config.exists(address_path + [address, 'web-options']) and address != 'web':
- config.delete(address_path + [address, 'web-options'])
-
- # Migrate "service dns dynamic address <interface> service <service> protocol dnsexit"
- # to "service dns dynamic address <interface> service <service> protocol dnsexit2"
- for address in config.list_nodes(address_path):
- for svc_cfg in config.list_nodes(address_path + [address, 'service']):
- if config.exists(address_path + [address, 'service', svc_cfg, 'protocol']):
- protocol = config.return_value(address_path + [address, 'service', svc_cfg, 'protocol'])
- if protocol == 'dnsexit':
- config.set(address_path + [address, 'service', svc_cfg, 'protocol'], 'dnsexit2')
+ # T6950: Can't migrate address if it doesn't exist
+ if config.exists(address_path):
+
+ # Remove "service dns dynamic address <interface> web-options ..." when <interface> != "web"
+ for address in config.list_nodes(address_path):
+ if config.exists(address_path + [address, 'web-options']) and address != 'web':
+ config.delete(address_path + [address, 'web-options'])
+
+ # Migrate "service dns dynamic address <interface> service <service> protocol dnsexit"
+ # to "service dns dynamic address <interface> service <service> protocol dnsexit2"
+ for address in config.list_nodes(address_path):
+ for svc_cfg in config.list_nodes(address_path + [address, 'service']):
+ if config.exists(address_path + [address, 'service', svc_cfg, 'protocol']):
+ protocol = config.return_value(address_path + [address, 'service', svc_cfg, 'protocol'])
+ if protocol == 'dnsexit':
+ config.set(address_path + [address, 'service', svc_cfg, 'protocol'], 'dnsexit2')
diff --git a/src/migration-scripts/quagga/11-to-12 b/src/migration-scripts/quagga/11-to-12
new file mode 100644
index 000000000..8ae2023a1
--- /dev/null
+++ b/src/migration-scripts/quagga/11-to-12
@@ -0,0 +1,75 @@
+# 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/>.
+
+# T6747:
+# - Migrate static BFD configuration to match FRR possibillities
+# - Consolidate static multicast routing configuration under a new node
+
+from vyos.configtree import ConfigTree
+
+static_base = ['protocols', 'static']
+
+def migrate(config: ConfigTree) -> None:
+ # Check for static route/route6 configuration
+ # Migrate static BFD configuration to match FRR possibillities
+ for route_route6 in ['route', 'route6']:
+ route_route6_base = static_base + [route_route6]
+ if not config.exists(route_route6_base):
+ continue
+
+ for prefix in config.list_nodes(route_route6_base):
+ next_hop_base = route_route6_base + [prefix, 'next-hop']
+ if not config.exists(next_hop_base):
+ continue
+
+ for next_hop in config.list_nodes(next_hop_base):
+ multi_hop_base = next_hop_base + [next_hop, 'bfd', 'multi-hop']
+
+ if not config.exists(multi_hop_base):
+ continue
+
+ mh_source_base = multi_hop_base + ['source']
+ source = None
+ profile = None
+ for src_ip in config.list_nodes(mh_source_base):
+ source = src_ip
+ if config.exists(mh_source_base + [source, 'profile']):
+ profile = config.return_value(mh_source_base + [source, 'profile'])
+ # FRR only supports one source, we will use the first one
+ break
+
+ config.delete(multi_hop_base)
+ config.set(multi_hop_base + ['source-address'], value=source)
+ config.set(next_hop_base + [next_hop, 'bfd', 'profile'], value=profile)
+
+ # Consolidate static multicast routing configuration under a new node
+ if config.exists(static_base + ['multicast']):
+ for mroute in ['interface-route', 'route']:
+ mroute_base = static_base + ['multicast', mroute]
+ if not config.exists(mroute_base):
+ continue
+ config.set(static_base + ['mroute'])
+ config.set_tag(static_base + ['mroute'])
+ for route in config.list_nodes(mroute_base):
+ config.copy(mroute_base + [route], static_base + ['mroute', route])
+
+ mroute_base = static_base + ['mroute']
+ if config.exists(mroute_base):
+ for mroute in config.list_nodes(mroute_base):
+ interface_path = mroute_base + [mroute, 'next-hop-interface']
+ if config.exists(interface_path):
+ config.rename(interface_path, 'interface')
+
+ config.delete(static_base + ['multicast'])
diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py
index bdc16de15..1da112673 100755
--- a/src/op_mode/image_installer.py
+++ b/src/op_mode/image_installer.py
@@ -33,14 +33,13 @@ from errno import ENOSPC
from psutil import disk_partitions
from vyos.configtree import ConfigTree
-from vyos.configquery import ConfigTreeQuery
from vyos.remote import download
from vyos.system import disk, grub, image, compat, raid, SYSTEM_CFG_VER
from vyos.template import render
from vyos.utils.io import ask_input, ask_yes_no, select_entry
from vyos.utils.file import chmod_2775
-from vyos.utils.process import cmd, run
-from vyos.version import get_remote_version, get_version_data
+from vyos.utils.process import cmd, run, rc_cmd
+from vyos.version import get_version_data
# define text messages
MSG_ERR_NOT_LIVE: str = 'The system is already installed. Please use "add system image" instead.'
@@ -99,6 +98,7 @@ FILE_ROOTFS_SRC: str = '/usr/lib/live/mount/medium/live/filesystem.squashfs'
ISO_DOWNLOAD_PATH: str = '/tmp/vyos_installation.iso'
external_download_script = '/usr/libexec/vyos/simple-download.py'
+external_latest_image_url_script = '/usr/libexec/vyos/latest-image-url.py'
# default boot variables
DEFAULT_BOOT_VARS: dict[str, str] = {
@@ -532,10 +532,10 @@ def download_file(local_file: str, remote_path: str, vrf: str,
download(local_file, remote_path, progressbar=progressbar,
check_space=check_space, raise_error=True)
else:
- vrf_cmd = f'REMOTE_USERNAME={username} REMOTE_PASSWORD={password} \
- ip vrf exec {vrf} {external_download_script} \
- --local-file {local_file} --remote-path {remote_path}'
- cmd(vrf_cmd)
+ remote_auth = f'REMOTE_USERNAME={username} REMOTE_PASSWORD={password}'
+ vrf_cmd = f'ip vrf exec {vrf} {external_download_script} \
+ --local-file {local_file} --remote-path {remote_path}'
+ cmd(vrf_cmd, auth=remote_auth)
def image_fetch(image_path: str, vrf: str = None,
username: str = '', password: str = '',
@@ -550,11 +550,15 @@ def image_fetch(image_path: str, vrf: str = None,
"""
# Latest version gets url from configured "system update-check url"
if image_path == 'latest':
- config = ConfigTreeQuery()
- if config.exists('system update-check url'):
- configured_url_version = config.value('system update-check url')
- remote_url_list = get_remote_version(configured_url_version)
- image_path = remote_url_list[0].get('url')
+ command = external_latest_image_url_script
+ if vrf:
+ command = f'REMOTE_USERNAME={username} REMOTE_PASSWORD={password} \
+ ip vrf exec {vrf} ' + command
+ code, output = rc_cmd(command)
+ if code:
+ print(output)
+ exit(MSG_INFO_INSTALL_EXIT)
+ image_path = output if output else image_path
try:
# check a type of path
diff --git a/src/services/vyos-configd b/src/services/vyos-configd
index d977ba2cb..ecad85801 100755
--- a/src/services/vyos-configd
+++ b/src/services/vyos-configd
@@ -37,6 +37,7 @@ from vyos.configsource import ConfigSourceString
from vyos.configsource import ConfigSourceError
from vyos.configdiff import get_commit_scripts
from vyos.config import Config
+from vyos.frrender import FRRender
from vyos import ConfigError
CFG_GROUP = 'vyattacfg'
@@ -209,6 +210,9 @@ def initialization(socket):
scripts_called = []
setattr(config, 'scripts_called', scripts_called)
+ if not hasattr(config, 'frrender_cls'):
+ setattr(config, 'frrender_cls', FRRender())
+
return config
@@ -326,5 +330,9 @@ if __name__ == '__main__':
if message['last'] and config:
scripts_called = getattr(config, 'scripts_called', [])
logger.debug(f'scripts_called: {scripts_called}')
+
+ if hasattr(config, 'frrender_cls') and res == R_SUCCESS:
+ frrender_cls = getattr(config, 'frrender_cls')
+ frrender_cls.apply()
else:
logger.critical(f'Unexpected message: {message}')
diff --git a/src/tests/test_initial_setup.py b/src/tests/test_initial_setup.py
index 4cd5fb169..7737f9df5 100644
--- a/src/tests/test_initial_setup.py
+++ b/src/tests/test_initial_setup.py
@@ -92,8 +92,8 @@ class TestInitialSetup(TestCase):
vis.set_default_gateway(self.config, '192.0.2.1')
self.assertTrue(self.config.exists(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop', '192.0.2.1']))
- self.assertTrue(self.xml.is_tag(['protocols', 'static', 'multicast', 'route', '0.0.0.0/0', 'next-hop']))
- self.assertTrue(self.xml.is_tag(['protocols', 'static', 'multicast', 'route']))
+ self.assertTrue(self.xml.is_tag(['protocols', 'static', 'mroute', '0.0.0.0/0', 'next-hop']))
+ self.assertTrue(self.xml.is_tag(['protocols', 'static', 'mroute']))
if __name__ == "__main__":
unittest.main()
diff --git a/src/validators/ether-type b/src/validators/ether-type
new file mode 100644
index 000000000..926db26d3
--- /dev/null
+++ b/src/validators/ether-type
@@ -0,0 +1,37 @@
+#!/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 re
+from sys import argv,exit
+
+if __name__ == '__main__':
+ if len(argv) != 2:
+ exit(1)
+
+ input = argv[1]
+ try:
+ # ethertype can be in the range 1 - 65535
+ if int(input) in range(1, 65536):
+ exit(0)
+ except ValueError:
+ pass
+
+ pattern = "!?\\b(all|ip|ipv6|ipx|802.1Q|802_2|802_3|aarp|aoe|arp|atalk|dec|lat|localtalk|rarp|snap|x25)\\b"
+ if re.match(pattern, input):
+ exit(0)
+
+ print(f'Error: {input} is not a valid ether type or protocol.')
+ exit(1)