diff options
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-x | src/conf_mode/interfaces_bonding.py | 34 | ||||
-rwxr-xr-x | src/conf_mode/interfaces_ethernet.py | 32 | ||||
-rwxr-xr-x | src/conf_mode/policy.py | 129 | ||||
-rwxr-xr-x | src/conf_mode/protocols_babel.py | 79 | ||||
-rwxr-xr-x | src/conf_mode/protocols_bfd.py | 40 | ||||
-rwxr-xr-x | src/conf_mode/protocols_bgp.py | 136 | ||||
-rwxr-xr-x | src/conf_mode/protocols_eigrp.py | 81 | ||||
-rwxr-xr-x | src/conf_mode/protocols_isis.py | 142 | ||||
-rwxr-xr-x | src/conf_mode/protocols_mpls.py | 42 | ||||
-rw-r--r-- | src/conf_mode/protocols_openfabric.py | 63 | ||||
-rwxr-xr-x | src/conf_mode/protocols_ospf.py | 133 | ||||
-rwxr-xr-x | src/conf_mode/protocols_ospfv3.py | 124 | ||||
-rwxr-xr-x | src/conf_mode/protocols_rip.py | 78 | ||||
-rwxr-xr-x | src/conf_mode/protocols_ripng.py | 63 | ||||
-rwxr-xr-x | src/conf_mode/protocols_rpki.py | 49 | ||||
-rwxr-xr-x | src/conf_mode/protocols_segment-routing.py | 92 | ||||
-rwxr-xr-x | src/conf_mode/protocols_static.py | 82 | ||||
-rwxr-xr-x | src/conf_mode/protocols_static_multicast.py | 133 | ||||
-rwxr-xr-x | src/conf_mode/service_snmp.py | 11 | ||||
-rwxr-xr-x | src/conf_mode/vrf.py | 23 |
20 files changed, 417 insertions, 1149 deletions
diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py index 633fb797c..adea8fc63 100755 --- a/src/conf_mode/interfaces_bonding.py +++ b/src/conf_mode/interfaces_bonding.py @@ -17,6 +17,7 @@ from sys import exit from vyos.config import Config +from vyos.configdict import get_frrender_dict from vyos.configdict import get_interface_dict from vyos.configdict import is_node_changed from vyos.configdict import leaf_node_changed @@ -30,10 +31,10 @@ from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf +from vyos.frrender import FRRender from vyos.ifconfig import BondIf from vyos.ifconfig.ethernet import EthernetIf from vyos.ifconfig import Section -from vyos.template import render_to_string from vyos.utils.assertion import assert_mac from vyos.utils.dict import dict_search from vyos.utils.dict import dict_to_paths_values @@ -42,9 +43,9 @@ from vyos.configdict import has_address_configured from vyos.configdict import has_vrf_configured from vyos.configdep import set_dependents, call_dependents from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() def get_bond_mode(mode): if mode == 'round-robin': @@ -87,10 +88,13 @@ def get_config(config=None): bond['mode'] = get_bond_mode(bond['mode']) tmp = is_node_changed(conf, base + [ifname, 'mode']) - if tmp: bond['shutdown_required'] = {} + if tmp: bond.update({'shutdown_required' : {}}) tmp = is_node_changed(conf, base + [ifname, 'lacp-rate']) - if tmp: bond['shutdown_required'] = {} + if tmp: bond.update({'shutdown_required' : {}}) + + tmp = is_node_changed(conf, base + [ifname, 'evpn']) + if tmp: bond.update({'frrender' : get_frrender_dict(conf)}) # determine which members have been removed interfaces_removed = leaf_node_changed(conf, base + [ifname, 'member', 'interface']) @@ -260,16 +264,16 @@ def verify(bond): return None def generate(bond): - bond['frr_zebra_config'] = '' - if 'deleted' not in bond: - bond['frr_zebra_config'] = render_to_string('frr/evpn.mh.frr.j2', bond) + if 'frrender' in bond: + frrender.generate(bond['frrender']) return None def apply(bond): - ifname = bond['ifname'] - b = BondIf(ifname) + if 'frrender' in bond: + frrender.apply() + + b = BondIf(bond['ifname']) if 'deleted' in bond: - # delete interface b.remove() else: b.update(bond) @@ -281,16 +285,6 @@ def apply(bond): raise ConfigError('Error in updating ethernet interface ' 'after deleting it from bond') - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(frr.mgmt_daemon) - frr_cfg.modify_section(f'^interface {ifname}', stop_pattern='^exit', remove_stop_mark=True) - if 'frr_zebra_config' in bond: - frr_cfg.add_before(frr.default_add_before, bond['frr_zebra_config']) - frr_cfg.commit_configuration() - return None if __name__ == '__main__': diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py index edbbb00c9..6a035e9e9 100755 --- a/src/conf_mode/interfaces_ethernet.py +++ b/src/conf_mode/interfaces_ethernet.py @@ -20,6 +20,7 @@ from sys import exit from vyos.base import Warning from vyos.config import Config +from vyos.configdict import get_frrender_dict from vyos.configdict import get_interface_dict from vyos.configdict import is_node_changed from vyos.configverify import verify_address @@ -33,17 +34,17 @@ from vyos.configverify import verify_vrf from vyos.configverify import verify_bond_bridge_member from vyos.configverify import verify_eapol from vyos.ethtool import Ethtool +from vyos.frrender import FRRender from vyos.ifconfig import EthernetIf from vyos.ifconfig import BondIf -from vyos.template import render_to_string from vyos.utils.dict import dict_search from vyos.utils.dict import dict_to_paths_values from vyos.utils.dict import dict_set from vyos.utils.dict import dict_delete from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() def update_bond_options(conf: Config, eth_conf: dict) -> list: """ @@ -164,6 +165,9 @@ def get_config(config=None): tmp = is_node_changed(conf, base + [ifname, 'duplex']) if tmp: ethernet.update({'speed_duplex_changed': {}}) + tmp = is_node_changed(conf, base + [ifname, 'evpn']) + if tmp: ethernet.update({'frrender' : get_frrender_dict(conf)}) + return ethernet def verify_speed_duplex(ethernet: dict, ethtool: Ethtool): @@ -318,32 +322,20 @@ def verify_ethernet(ethernet): return None def generate(ethernet): - if 'deleted' in ethernet: - return None - - ethernet['frr_zebra_config'] = '' - if 'deleted' not in ethernet: - ethernet['frr_zebra_config'] = render_to_string('frr/evpn.mh.frr.j2', ethernet) - + if 'frrender' in ethernet: + frrender.generate(ethernet['frrender']) return None def apply(ethernet): - ifname = ethernet['ifname'] + if 'frrender' in ethernet: + frrender.apply() - e = EthernetIf(ifname) + e = EthernetIf(ethernet['ifname']) if 'deleted' in ethernet: - # delete interface e.remove() else: e.update(ethernet) - - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(frr.mgmt_daemon) - frr_cfg.modify_section(f'^interface {ifname}', stop_pattern='^exit', remove_stop_mark=True) - if 'frr_zebra_config' in ethernet: - frr_cfg.add_before(frr.default_add_before, ethernet['frr_zebra_config']) - frr_cfg.commit_configuration() + return None if __name__ == '__main__': try: diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py index aef9b96c4..e6b6d474a 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2022 VyOS maintainers and contributors +# Copyright (C) 2021-2024 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -17,15 +17,16 @@ from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.template import render_to_string +from vyos.configdict import get_frrender_dict +from vyos.configverify import has_frr_protocol_in_dict +from vyos.frrender import FRRender +from vyos.frrender import frr_protocols from vyos.utils.dict import dict_search from vyos import ConfigError -from vyos import frr from vyos import airbag - airbag.enable() +frrender = FRRender() def community_action_compatibility(actions: dict) -> bool: """ @@ -87,31 +88,27 @@ def get_config(config=None): else: conf = Config() - base = ['policy'] - policy = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - - # We also need some additional information from the config, prefix-lists - # and route-maps for instance. They will be used in verify(). - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = conf.get_config_dict(['protocols'], key_mangling=('-', '_'), - no_tag_node_value_mangle=True) - # Merge policy dict into "regular" config dict - policy = dict_merge(tmp, policy) - return policy - - -def verify(policy): - if not policy: + return get_frrender_dict(conf) + + +def verify(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'policy'): return None - for policy_type in ['access_list', 'access_list6', 'as_path_list', - 'community_list', 'extcommunity_list', - 'large_community_list', - 'prefix_list', 'prefix_list6', 'route_map']: + policy_types = ['access_list', 'access_list6', 'as_path_list', + 'community_list', 'extcommunity_list', + 'large_community_list', 'prefix_list', + 'prefix_list6', 'route_map'] + + policy = config_dict['policy'] + for protocol in frr_protocols: + if protocol not in config_dict: + continue + if 'protocol' not in policy: + policy.update({'protocol': {}}) + policy['protocol'].update({protocol : config_dict[protocol]}) + + for policy_type in policy_types: # Bail out early and continue with next policy type if policy_type not in policy: continue @@ -246,69 +243,33 @@ def verify(policy): # When the "routing policy" changes and policies, route-maps etc. are deleted, # it is our responsibility to verify that the policy can not be deleted if it # is used by any routing protocol - if 'protocols' in policy: - for policy_type in ['access_list', 'access_list6', 'as_path_list', - 'community_list', - 'extcommunity_list', 'large_community_list', - 'prefix_list', 'route_map']: - if policy_type in policy: - for policy_name in list(set(routing_policy_find(policy_type, - policy[ - 'protocols']))): - found = False - if policy_name in policy[policy_type]: - found = True - # BGP uses prefix-list for selecting both an IPv4 or IPv6 AFI related - # list - we need to go the extra mile here and check both prefix-lists - if policy_type == 'prefix_list' and 'prefix_list6' in policy and policy_name in \ - policy['prefix_list6']: - found = True - if not found: - tmp = policy_type.replace('_', '-') - raise ConfigError( - f'Can not delete {tmp} "{policy_name}", still in use!') - - return None - + # Check if any routing protocol is activated + if 'protocol' in policy: + for policy_type in policy_types: + for policy_name in list(set(routing_policy_find(policy_type, policy['protocol']))): + found = False + if policy_type in policy and policy_name in policy[policy_type]: + found = True + # BGP uses prefix-list for selecting both an IPv4 or IPv6 AFI related + # list - we need to go the extra mile here and check both prefix-lists + if policy_type == 'prefix_list' and 'prefix_list6' in policy and policy_name in \ + policy['prefix_list6']: + found = True + if not found: + tmp = policy_type.replace('_', '-') + raise ConfigError( + f'Can not delete {tmp} "{policy_name}", still in use!') -def generate(policy): - if not policy: - return None - policy['new_frr_config'] = render_to_string('frr/policy.frr.j2', policy) return None -def apply(policy): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(frr.bgp_daemon) - frr_cfg.modify_section(r'^bgp as-path access-list .*') - frr_cfg.modify_section(r'^bgp community-list .*') - frr_cfg.modify_section(r'^bgp extcommunity-list .*') - frr_cfg.modify_section(r'^bgp large-community-list .*') - frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit', - remove_stop_mark=True) - if 'new_frr_config' in policy: - frr_cfg.add_before(frr.default_add_before, policy['new_frr_config']) - frr_cfg.commit_configuration(frr.bgp_daemon) - - # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(frr.zebra_daemon) - frr_cfg.modify_section(r'^access-list .*') - frr_cfg.modify_section(r'^ipv6 access-list .*') - frr_cfg.modify_section(r'^ip prefix-list .*') - frr_cfg.modify_section(r'^ipv6 prefix-list .*') - frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit', - remove_stop_mark=True) - if 'new_frr_config' in policy: - frr_cfg.add_before(frr.default_add_before, policy['new_frr_config']) - frr_cfg.commit_configuration(frr.zebra_daemon) +def generate(config_dict): + frrender.generate(config_dict) +def apply(config_dict): + frrender.apply() return None - if __name__ == '__main__': try: c = get_config() diff --git a/src/conf_mode/protocols_babel.py b/src/conf_mode/protocols_babel.py index 06fd9b9b6..f9d5e45a0 100755 --- a/src/conf_mode/protocols_babel.py +++ b/src/conf_mode/protocols_babel.py @@ -17,63 +17,33 @@ from sys import exit from vyos.config import Config -from vyos.config import config_dict_merge -from vyos.configdict import dict_merge -from vyos.configdict import node_changed +from vyos.configdict import get_frrender_dict +from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_access_list from vyos.configverify import verify_prefix_list +from vyos.frrender import FRRender from vyos.utils.dict import dict_search -from vyos.template import render_to_string from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() + def get_config(config=None): if config: conf = config else: conf = Config() - base = ['protocols', 'babel'] - babel = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True) - - # FRR has VRF support for different routing daemons. As interfaces belong - # to VRFs - or the global VRF, we need to check for changed interfaces so - # that they will be properly rendered for the FRR config. Also this eases - # removal of interfaces from the running configuration. - interfaces_removed = node_changed(conf, base + ['interface']) - if interfaces_removed: - babel['interface_removed'] = list(interfaces_removed) - - # Bail out early if configuration tree does not exist - if not conf.exists(base): - babel.update({'deleted' : ''}) - return babel - - # We have gathered the dict representation of the CLI, but there are default - # values which we need to update into the dictionary retrieved. - default_values = conf.get_config_defaults(base, key_mangling=('-', '_'), - get_first_key=True, - recursive=True) - # merge in default values - babel = config_dict_merge(default_values, babel) + return get_frrender_dict(conf) - # We also need some additional information from the config, prefix-lists - # and route-maps for instance. They will be used in verify(). - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = conf.get_config_dict(['policy']) - # Merge policy dict into "regular" config dict - babel = dict_merge(tmp, babel) - return babel - -def verify(babel): - if not babel: +def verify(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'babel'): return None + babel = config_dict['babel'] + babel['policy'] = config_dict['policy'] + # verify distribute_list if "distribute_list" in babel: acl_keys = { @@ -120,30 +90,11 @@ def verify(babel): verify_prefix_list(prefix_list, babel, version='6' if address_family == 'ipv6' else '') -def generate(babel): - if not babel or 'deleted' in babel: - return None - - babel['new_frr_config'] = render_to_string('frr/babeld.frr.j2', babel) - return None - -def apply(babel): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - frr_cfg.load_configuration(frr.babel_daemon) - frr_cfg.modify_section('^router babel', stop_pattern='^exit', remove_stop_mark=True) - - for key in ['interface', 'interface_removed']: - if key not in babel: - continue - for interface in babel[key]: - frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) - - if 'new_frr_config' in babel: - frr_cfg.add_before(frr.default_add_before, babel['new_frr_config']) - frr_cfg.commit_configuration(frr.babel_daemon) +def generate(config_dict): + frrender.generate(config_dict) +def apply(config_dict): + frrender.apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index d94ec6a0d..a0d7fdfb5 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -15,36 +15,31 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from vyos.config import Config +from vyos.configdict import get_frrender_dict from vyos.configverify import verify_vrf +from vyos.configverify import has_frr_protocol_in_dict +from vyos.frrender import FRRender from vyos.template import is_ipv6 -from vyos.template import render_to_string from vyos.utils.network import is_ipv6_link_local from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() + def get_config(config=None): if config: conf = config else: conf = Config() - base = ['protocols', 'bfd'] - bfd = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - # Bail out early if configuration tree does not exist - if not conf.exists(base): - return bfd - - bfd = conf.merge_defaults(bfd, recursive=True) - return bfd + return get_frrender_dict(conf) -def verify(bfd): - if not bfd: +def verify(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'bfd'): return None + bfd = config_dict['bfd'] if 'peer' in bfd: for peer, peer_config in bfd['peer'].items(): # IPv6 link local peers require an explicit local address/interface @@ -83,20 +78,11 @@ def verify(bfd): return None -def generate(bfd): - if not bfd: - return None - bfd['new_frr_config'] = render_to_string('frr/bfdd.frr.j2', bfd) - -def apply(bfd): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(frr.bfd_daemon) - frr_cfg.modify_section('^bfd', stop_pattern='^exit', remove_stop_mark=True) - if 'new_frr_config' in bfd: - frr_cfg.add_before(frr.default_add_before, bfd['new_frr_config']) - frr_cfg.commit_configuration(frr.bfd_daemon) +def generate(config_dict): + frrender.generate(config_dict) +def apply(config_dict): + frrender.apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index e5c46aee6..989cf9b5c 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -19,92 +19,35 @@ from sys import argv from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.configdict import node_changed +from vyos.configdict import get_frrender_dict +from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_prefix_list from vyos.configverify import verify_route_map from vyos.configverify import verify_vrf +from vyos.frrender import FRRender from vyos.template import is_ip from vyos.template import is_interface -from vyos.template import render_to_string from vyos.utils.dict import dict_search from vyos.utils.network import get_interface_vrf from vyos.utils.network import is_addr_assigned from vyos.utils.process import process_named_running -from vyos.utils.process import call from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() + +vrf = None +if len(argv) > 1: + vrf = argv[1] + def get_config(config=None): if config: conf = config else: conf = Config() - vrf = None - if len(argv) > 1: - vrf = argv[1] - - base_path = ['protocols', 'bgp'] - - # eqivalent of the C foo ? 'a' : 'b' statement - base = vrf and ['vrf', 'name', vrf, 'protocols', 'bgp'] or base_path - bgp = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - - bgp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'], - key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - - # Remove per interface MPLS configuration - get a list if changed - # nodes under the interface tagNode - interfaces_removed = node_changed(conf, base + ['interface']) - if interfaces_removed: - bgp['interface_removed'] = list(interfaces_removed) - - # Assign the name of our VRF context. This MUST be done before the return - # statement below, else on deletion we will delete the default instance - # instead of the VRF instance. - if vrf: - bgp.update({'vrf' : vrf}) - # We can not delete the BGP VRF instance if there is a L3VNI configured - # FRR L3VNI must be deleted first otherwise we will see error: - # "FRR error: Please unconfigure l3vni 3000" - tmp = ['vrf', 'name', vrf, 'vni'] - if conf.exists_effective(tmp): - bgp.update({'vni' : conf.return_effective_value(tmp)}) - # We can safely delete ourself from the dependent vrf list - if vrf in bgp['dependent_vrfs']: - del bgp['dependent_vrfs'][vrf] - - bgp['dependent_vrfs'].update({'default': {'protocols': { - 'bgp': conf.get_config_dict(base_path, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True)}}}) - - if not conf.exists(base): - # If bgp instance is deleted then mark it - bgp.update({'deleted' : ''}) - return bgp - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - bgp = conf.merge_defaults(bgp, recursive=True) - - # We also need some additional information from the config, prefix-lists - # and route-maps for instance. They will be used in verify(). - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = conf.get_config_dict(['policy']) - # Merge policy dict into "regular" config dict - bgp = dict_merge(tmp, bgp) - - return bgp - + return get_frrender_dict(conf) def verify_vrf_as_import(search_vrf_name: str, afi_name: str, vrfs_config: dict) -> bool: """ @@ -237,7 +180,18 @@ def verify_afi(peer_config, bgp_config): if tmp: return True return False -def verify(bgp): +def verify(config_dict): + global vrf + if not has_frr_protocol_in_dict(config_dict, 'bgp', vrf): + return None + + # eqivalent of the C foo ? 'a' : 'b' statement + bgp = vrf and config_dict['vrf']['name'][vrf]['protocols']['bgp'] or config_dict['bgp'] + bgp['policy'] = config_dict['policy'] + + if vrf: + bgp['vrf'] = vrf + if 'deleted' in bgp: if 'vrf' in bgp: # Cannot delete vrf if it exists in import vrf list in other vrfs @@ -252,8 +206,9 @@ def verify(bgp): for vrf, vrf_options in bgp['dependent_vrfs'].items(): if vrf != 'default': if dict_search('protocols.bgp', vrf_options): - raise ConfigError('Cannot delete default BGP instance, ' \ - 'dependent VRF instance(s) exist(s)!') + dependent_vrfs = ', '.join(bgp['dependent_vrfs'].keys()) + raise ConfigError(f'Cannot delete default BGP instance, ' \ + f'dependent VRF instance(s): {dependent_vrfs}') if 'vni' in vrf_options: raise ConfigError('Cannot delete default BGP instance, ' \ 'dependent L3VNI exists!') @@ -602,44 +557,11 @@ def verify(bgp): return None -def generate(bgp): - if not bgp or 'deleted' in bgp: - return None - - bgp['frr_bgpd_config'] = render_to_string('frr/bgpd.frr.j2', bgp) - return None - -def apply(bgp): - if 'deleted' in bgp: - # We need to ensure that the L3VNI is deleted first. - # This is not possible with old config backend - # priority bug - if {'vrf', 'vni'} <= set(bgp): - call('vtysh -c "conf t" -c "vrf {vrf}" -c "no vni {vni}"'.format(**bgp)) - - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - # Generate empty helper string which can be ammended to FRR commands, it - # will be either empty (default VRF) or contain the "vrf <name" statement - vrf = '' - if 'vrf' in bgp: - vrf = ' vrf ' + bgp['vrf'] - - frr_cfg.load_configuration(frr.bgp_daemon) - - # Remove interface specific config - for key in ['interface', 'interface_removed']: - if key not in bgp: - continue - for interface in bgp[key]: - frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) - - frr_cfg.modify_section(f'^router bgp \d+{vrf}', stop_pattern='^exit', remove_stop_mark=True) - if 'frr_bgpd_config' in bgp: - frr_cfg.add_before(frr.default_add_before, bgp['frr_bgpd_config']) - frr_cfg.commit_configuration(frr.bgp_daemon) +def generate(config_dict): + frrender.generate(config_dict) +def apply(config_dict): + frrender.apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_eigrp.py b/src/conf_mode/protocols_eigrp.py index a948b47da..5d60e6bfd 100755 --- a/src/conf_mode/protocols_eigrp.py +++ b/src/conf_mode/protocols_eigrp.py @@ -18,14 +18,18 @@ from sys import exit from sys import argv from vyos.config import Config -from vyos.configdict import dict_merge +from vyos.configdict import get_frrender_dict +from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_vrf -from vyos.template import render_to_string +from vyos.frrender import FRRender from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() +vrf = None +if len(argv) > 1: + vrf = argv[1] def get_config(config=None): if config: @@ -33,48 +37,16 @@ def get_config(config=None): else: conf = Config() - vrf = None - if len(argv) > 1: - vrf = argv[1] + return get_frrender_dict(conf) - base_path = ['protocols', 'eigrp'] +def verify(config_dict): + global vrf + if not has_frr_protocol_in_dict(config_dict, 'eigrp', vrf): + return None # eqivalent of the C foo ? 'a' : 'b' statement - base = vrf and ['vrf', 'name', vrf, 'protocols', 'eigrp'] or base_path - eigrp = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - - # Assign the name of our VRF context. This MUST be done before the return - # statement below, else on deletion we will delete the default instance - # instead of the VRF instance. - if vrf: eigrp.update({'vrf' : vrf}) - - if not conf.exists(base): - eigrp.update({'deleted' : ''}) - if not vrf: - # We are running in the default VRF context, thus we can not delete - # our main EIGRP instance if there are dependent EIGRP VRF instances. - eigrp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'], - key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - - return eigrp - - # We also need some additional information from the config, prefix-lists - # and route-maps for instance. They will be used in verify(). - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = conf.get_config_dict(['policy']) - # Merge policy dict into "regular" config dict - eigrp = dict_merge(tmp, eigrp) - - return eigrp - -def verify(eigrp): - if not eigrp or 'deleted' in eigrp: - return + eigrp = vrf and config_dict['vrf']['name'][vrf]['protocols']['eigrp'] or config_dict['eigrp'] + eigrp['policy'] = config_dict['policy'] if 'system_as' not in eigrp: raise ConfigError('EIGRP system-as must be defined!') @@ -82,28 +54,11 @@ def verify(eigrp): if 'vrf' in eigrp: verify_vrf(eigrp) -def generate(eigrp): - if not eigrp or 'deleted' in eigrp: - return None - - eigrp['frr_eigrpd_config'] = render_to_string('frr/eigrpd.frr.j2', eigrp) - -def apply(eigrp): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - # Generate empty helper string which can be ammended to FRR commands, it - # will be either empty (default VRF) or contain the "vrf <name" statement - vrf = '' - if 'vrf' in eigrp: - vrf = ' vrf ' + eigrp['vrf'] - - frr_cfg.load_configuration(frr.eigrp_daemon) - frr_cfg.modify_section(f'^router eigrp \d+{vrf}', stop_pattern='^exit', remove_stop_mark=True) - if 'frr_eigrpd_config' in eigrp: - frr_cfg.add_before(frr.default_add_before, eigrp['frr_eigrpd_config']) - frr_cfg.commit_configuration(frr.eigrp_daemon) +def generate(config_dict): + frrender.generate(config_dict) +def apply(config_dict): + frrender.apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index 9b70c7329..e812770bc 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -18,75 +18,89 @@ from sys import exit from sys import argv from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.configdict import node_changed +from vyos.configdict import get_frrender_dict +from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_interface_exists +from vyos.frrender import FRRender from vyos.ifconfig import Interface from vyos.utils.dict import dict_search from vyos.utils.network import get_interface_config -from vyos.template import render_to_string from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() + +vrf = None +if len(argv) > 1: + vrf = argv[1] + def get_config(config=None): if config: conf = config else: conf = Config() - vrf = None - if len(argv) > 1: - vrf = argv[1] + return get_frrender_dict(conf) - base_path = ['protocols', 'isis'] - # eqivalent of the C foo ? 'a' : 'b' statement - base = vrf and ['vrf', 'name', vrf, 'protocols', 'isis'] or base_path - isis = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - - # Assign the name of our VRF context. This MUST be done before the return - # statement below, else on deletion we will delete the default instance - # instead of the VRF instance. - if vrf: isis['vrf'] = vrf - - # FRR has VRF support for different routing daemons. As interfaces belong - # to VRFs - or the global VRF, we need to check for changed interfaces so - # that they will be properly rendered for the FRR config. Also this eases - # removal of interfaces from the running configuration. - interfaces_removed = node_changed(conf, base + ['interface']) - if interfaces_removed: - isis['interface_removed'] = list(interfaces_removed) - - # Bail out early if configuration tree does no longer exist. this must - # be done after retrieving the list of interfaces to be removed. - if not conf.exists(base): - isis.update({'deleted' : ''}) - return isis - - # merge in default values - isis = conf.merge_defaults(isis, recursive=True) - - # We also need some additional information from the config, prefix-lists - # and route-maps for instance. They will be used in verify(). - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = conf.get_config_dict(['policy']) - # Merge policy dict into "regular" config dict - isis = dict_merge(tmp, isis) + # base_path = ['protocols', 'isis'] + + # # eqivalent of the C foo ? 'a' : 'b' statement + # base = vrf and ['vrf', 'name', vrf, 'protocols', 'isis'] or base_path + # isis = conf.get_config_dict(base, key_mangling=('-', '_'), + # get_first_key=True, + # no_tag_node_value_mangle=True) + + # # Assign the name of our VRF context. This MUST be done before the return + # # statement below, else on deletion we will delete the default instance + # # instead of the VRF instance. + # if vrf: isis['vrf'] = vrf + + # # FRR has VRF support for different routing daemons. As interfaces belong + # # to VRFs - or the global VRF, we need to check for changed interfaces so + # # that they will be properly rendered for the FRR config. Also this eases + # # removal of interfaces from the running configuration. + # interfaces_removed = node_changed(conf, base + ['interface']) + # if interfaces_removed: + # isis['interface_removed'] = list(interfaces_removed) + + # # Bail out early if configuration tree does no longer exist. this must + # # be done after retrieving the list of interfaces to be removed. + # if not conf.exists(base): + # isis.update({'deleted' : ''}) + # return isis + + # # merge in default values + # isis = conf.merge_defaults(isis, recursive=True) + + # # We also need some additional information from the config, prefix-lists + # # and route-maps for instance. They will be used in verify(). + # # + # # XXX: one MUST always call this without the key_mangling() option! See + # # vyos.configverify.verify_common_route_maps() for more information. + # tmp = conf.get_config_dict(['policy']) + # # Merge policy dict into "regular" config dict + # isis = dict_merge(tmp, isis) return isis -def verify(isis): - # bail out early - looks like removal from running config - if not isis or 'deleted' in isis: +def verify(config_dict): + global vrf + if not has_frr_protocol_in_dict(config_dict, 'isis', vrf): return None + # eqivalent of the C foo ? 'a' : 'b' statement + isis = vrf and config_dict['vrf']['name'][vrf]['protocols']['isis'] or config_dict['isis'] + isis['policy'] = config_dict['policy'] + + if 'deleted' in isis: + return None + + if vrf: + isis['vrf'] = vrf + if 'net' not in isis: raise ConfigError('Network entity is mandatory!') @@ -266,37 +280,11 @@ def verify(isis): return None -def generate(isis): - if not isis or 'deleted' in isis: - return None - - isis['frr_isisd_config'] = render_to_string('frr/isisd.frr.j2', isis) - return None - -def apply(isis): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - # Generate empty helper string which can be ammended to FRR commands, it - # will be either empty (default VRF) or contain the "vrf <name" statement - vrf = '' - if 'vrf' in isis: - vrf = ' vrf ' + isis['vrf'] - - frr_cfg.load_configuration(frr.isis_daemon) - frr_cfg.modify_section(f'^router isis VyOS{vrf}', stop_pattern='^exit', remove_stop_mark=True) - - for key in ['interface', 'interface_removed']: - if key not in isis: - continue - for interface in isis[key]: - frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) - - if 'frr_isisd_config' in isis: - frr_cfg.add_before(frr.default_add_before, isis['frr_isisd_config']) - - frr_cfg.commit_configuration(frr.isis_daemon) +def generate(config_dict): + frrender.generate(config_dict) +def apply(config_dict): + frrender.apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index 4a05b8044..2a691f4a4 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2022 VyOS maintainers and contributors +# Copyright (C) 2020-2024 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -20,33 +20,33 @@ from sys import exit from glob import glob from vyos.config import Config -from vyos.template import render_to_string +from vyos.configdict import get_frrender_dict +from vyos.configverify import has_frr_protocol_in_dict +from vyos.frrender import FRRender from vyos.utils.dict import dict_search from vyos.utils.file import read_file from vyos.utils.system import sysctl_write from vyos.configverify import verify_interface_exists from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() -config_file = r'/tmp/ldpd.frr' +frrender = FRRender() def get_config(config=None): if config: conf = config else: conf = Config() - base = ['protocols', 'mpls'] - mpls = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - return mpls + return get_frrender_dict(conf) -def verify(mpls): - # If no config, then just bail out early. - if not mpls: +def verify(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'mpls'): return None + mpls = config_dict['mpls'] + if 'interface' in mpls: for interface in mpls['interface']: verify_interface_exists(mpls, interface) @@ -68,24 +68,16 @@ def verify(mpls): return None -def generate(mpls): - # If there's no MPLS config generated, create dictionary key with no value. - if not mpls or 'deleted' in mpls: - return None +def generate(config_dict): + frrender.generate(config_dict) - mpls['frr_ldpd_config'] = render_to_string('frr/ldpd.frr.j2', mpls) - return None +def apply(config_dict): + frrender.apply() -def apply(mpls): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - frr_cfg.load_configuration(frr.ldpd_daemon) - frr_cfg.modify_section(f'^mpls ldp', stop_pattern='^exit', remove_stop_mark=True) + if not has_frr_protocol_in_dict(config_dict, 'mpls'): + return None - if 'frr_ldpd_config' in mpls: - frr_cfg.add_before(frr.default_add_before, mpls['frr_ldpd_config']) - frr_cfg.commit_configuration(frr.ldpd_daemon) + mpls = config_dict['mpls'] # Set number of entries in the platform label tables labels = '0' diff --git a/src/conf_mode/protocols_openfabric.py b/src/conf_mode/protocols_openfabric.py index b60117a02..3dc06ee68 100644 --- a/src/conf_mode/protocols_openfabric.py +++ b/src/conf_mode/protocols_openfabric.py @@ -18,14 +18,15 @@ from sys import exit from vyos.base import Warning from vyos.config import Config -from vyos.configdict import node_changed +from vyos.configdict import get_frrender_dict from vyos.configverify import verify_interface_exists -from vyos.template import render_to_string +from vyos.configverify import has_frr_protocol_in_dict +from vyos.frrender import FRRender from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() def get_config(config=None): if config: @@ -33,32 +34,14 @@ def get_config(config=None): else: conf = Config() - base_path = ['protocols', 'openfabric'] + return get_frrender_dict(conf) - openfabric = conf.get_config_dict(base_path, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - - # Remove per domain MPLS configuration - get a list of all changed Openfabric domains - # (removed and added) so that they will be properly rendered for the FRR config. - openfabric['domains_all'] = list(conf.list_nodes(' '.join(base_path) + f' domain') + - node_changed(conf, base_path + ['domain'])) - - # Get a list of all interfaces - openfabric['interfaces_all'] = [] - for domain in openfabric['domains_all']: - interfaces_modified = list(node_changed(conf, base_path + ['domain', domain, 'interface']) + - conf.list_nodes(' '.join(base_path) + f' domain {domain} interface')) - openfabric['interfaces_all'].extend(interfaces_modified) - - if not conf.exists(base_path): - openfabric.update({'deleted': ''}) - - return openfabric +def verify(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'openfabric'): + return None -def verify(openfabric): - # bail out early - looks like removal from running config - if not openfabric or 'deleted' in openfabric: + openfabric = config_dict['openfabric'] + if 'deleted' in openfabric: return None if 'net' not in openfabric: @@ -107,29 +90,11 @@ def verify(openfabric): return None -def generate(openfabric): - if not openfabric or 'deleted' in openfabric: - return None - - openfabric['frr_fabricd_config'] = render_to_string('frr/fabricd.frr.j2', openfabric) - return None - -def apply(openfabric): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - frr_cfg.load_configuration(frr.openfabric_daemon) - for domain in openfabric['domains_all']: - frr_cfg.modify_section(f'^router openfabric {domain}', stop_pattern='^exit', remove_stop_mark=True) - - for interface in openfabric['interfaces_all']: - frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) - - if 'frr_fabricd_config' in openfabric: - frr_cfg.add_before(frr.default_add_before, openfabric['frr_fabricd_config']) - - frr_cfg.commit_configuration(frr.openfabric_daemon) +def generate(config_dict): + frrender.generate(config_dict) +def apply(config_dict): + frrender.apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index 44817b9b7..32ad5e497 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -18,20 +18,23 @@ from sys import exit from sys import argv from vyos.config import Config -from vyos.config import config_dict_merge -from vyos.configdict import dict_merge -from vyos.configdict import node_changed +from vyos.configdict import get_frrender_dict from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_route_map from vyos.configverify import verify_interface_exists from vyos.configverify import verify_access_list -from vyos.template import render_to_string +from vyos.configverify import has_frr_protocol_in_dict +from vyos.frrender import FRRender from vyos.utils.dict import dict_search from vyos.utils.network import get_interface_config from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() + +vrf = None +if len(argv) > 1: + vrf = argv[1] def get_config(config=None): if config: @@ -39,85 +42,16 @@ def get_config(config=None): else: conf = Config() - vrf = None - if len(argv) > 1: - vrf = argv[1] + return get_frrender_dict(conf) - base_path = ['protocols', 'ospf'] +def verify(config_dict): + global vrf + if not has_frr_protocol_in_dict(config_dict, 'ospf', vrf): + return None # eqivalent of the C foo ? 'a' : 'b' statement - base = vrf and ['vrf', 'name', vrf, 'protocols', 'ospf'] or base_path - ospf = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True) - - # Assign the name of our VRF context. This MUST be done before the return - # statement below, else on deletion we will delete the default instance - # instead of the VRF instance. - if vrf: ospf['vrf'] = vrf - - # FRR has VRF support for different routing daemons. As interfaces belong - # to VRFs - or the global VRF, we need to check for changed interfaces so - # that they will be properly rendered for the FRR config. Also this eases - # removal of interfaces from the running configuration. - interfaces_removed = node_changed(conf, base + ['interface']) - if interfaces_removed: - ospf['interface_removed'] = list(interfaces_removed) - - # Bail out early if configuration tree does no longer exist. this must - # be done after retrieving the list of interfaces to be removed. - if not conf.exists(base): - ospf.update({'deleted' : ''}) - return ospf - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = conf.get_config_defaults(**ospf.kwargs, recursive=True) - - # We have to cleanup the default dict, as default values could enable features - # which are not explicitly enabled on the CLI. Example: default-information - # originate comes with a default metric-type of 2, which will enable the - # entire default-information originate tree, even when not set via CLI so we - # need to check this first and probably drop that key. - if dict_search('default_information.originate', ospf) is None: - del default_values['default_information'] - if 'mpls_te' not in ospf: - del default_values['mpls_te'] - if 'graceful_restart' not in ospf: - del default_values['graceful_restart'] - for area_num in default_values.get('area', []): - if dict_search(f'area.{area_num}.area_type.nssa', ospf) is None: - del default_values['area'][area_num]['area_type']['nssa'] - - for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']: - if dict_search(f'redistribute.{protocol}', ospf) is None: - del default_values['redistribute'][protocol] - if not bool(default_values['redistribute']): - del default_values['redistribute'] - - for interface in ospf.get('interface', []): - # We need to reload the defaults on every pass b/c of - # hello-multiplier dependency on dead-interval - # If hello-multiplier is set, we need to remove the default from - # dead-interval. - if 'hello_multiplier' in ospf['interface'][interface]: - del default_values['interface'][interface]['dead_interval'] - - ospf = config_dict_merge(default_values, ospf) - - # We also need some additional information from the config, prefix-lists - # and route-maps for instance. They will be used in verify(). - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = conf.get_config_dict(['policy']) - # Merge policy dict into "regular" config dict - ospf = dict_merge(tmp, ospf) - - return ospf - -def verify(ospf): - if not ospf: - return None + ospf = vrf and config_dict['vrf']['name'][vrf]['protocols']['ospf'] or config_dict['ospf'] + ospf['policy'] = config_dict['policy'] verify_common_route_maps(ospf) @@ -164,8 +98,7 @@ def verify(ospf): # interface is bound to our requesting VRF. Due to the VyOS # priorities the interface is bound to the VRF after creation of # the VRF itself, and before any routing protocol is configured. - if 'vrf' in ospf: - vrf = ospf['vrf'] + if vrf: tmp = get_interface_config(interface) if 'master' not in tmp or tmp['master'] != vrf: raise ConfigError(f'Interface "{interface}" is not a member of VRF "{vrf}"!') @@ -244,37 +177,11 @@ def verify(ospf): return None -def generate(ospf): - if not ospf or 'deleted' in ospf: - return None - - ospf['frr_ospfd_config'] = render_to_string('frr/ospfd.frr.j2', ospf) - return None - -def apply(ospf): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - # Generate empty helper string which can be ammended to FRR commands, it - # will be either empty (default VRF) or contain the "vrf <name" statement - vrf = '' - if 'vrf' in ospf: - vrf = ' vrf ' + ospf['vrf'] - - frr_cfg.load_configuration(frr.ospf_daemon) - frr_cfg.modify_section(f'^router ospf{vrf}', stop_pattern='^exit', remove_stop_mark=True) - - for key in ['interface', 'interface_removed']: - if key not in ospf: - continue - for interface in ospf[key]: - frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) - - if 'frr_ospfd_config' in ospf: - frr_cfg.add_before(frr.default_add_before, ospf['frr_ospfd_config']) - - frr_cfg.commit_configuration(frr.ospf_daemon) +def generate(config_dict): + frrender.generate(config_dict) +def apply(config_dict): + frrender.apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py index 7bdab3017..038fcd2b4 100755 --- a/src/conf_mode/protocols_ospfv3.py +++ b/src/conf_mode/protocols_ospfv3.py @@ -18,96 +18,41 @@ from sys import exit from sys import argv from vyos.config import Config -from vyos.config import config_dict_merge -from vyos.configdict import dict_merge -from vyos.configdict import node_changed +from vyos.configdict import get_frrender_dict from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_route_map from vyos.configverify import verify_interface_exists -from vyos.template import render_to_string +from vyos.configverify import has_frr_protocol_in_dict +from vyos.frrender import FRRender from vyos.ifconfig import Interface from vyos.utils.dict import dict_search from vyos.utils.network import get_interface_config from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() + +vrf = None +if len(argv) > 1: + vrf = argv[1] + def get_config(config=None): if config: conf = config else: conf = Config() - vrf = None - if len(argv) > 1: - vrf = argv[1] + return get_frrender_dict(conf) - base_path = ['protocols', 'ospfv3'] +def verify(config_dict): + global vrf + if not has_frr_protocol_in_dict(config_dict, 'ospfv3', vrf): + return None # eqivalent of the C foo ? 'a' : 'b' statement - base = vrf and ['vrf', 'name', vrf, 'protocols', 'ospfv3'] or base_path - ospfv3 = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - - # Assign the name of our VRF context. This MUST be done before the return - # statement below, else on deletion we will delete the default instance - # instead of the VRF instance. - if vrf: ospfv3['vrf'] = vrf - - # FRR has VRF support for different routing daemons. As interfaces belong - # to VRFs - or the global VRF, we need to check for changed interfaces so - # that they will be properly rendered for the FRR config. Also this eases - # removal of interfaces from the running configuration. - interfaces_removed = node_changed(conf, base + ['interface']) - if interfaces_removed: - ospfv3['interface_removed'] = list(interfaces_removed) - - # Bail out early if configuration tree does no longer exist. this must - # be done after retrieving the list of interfaces to be removed. - if not conf.exists(base): - ospfv3.update({'deleted' : ''}) - return ospfv3 - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = conf.get_config_defaults(**ospfv3.kwargs, - recursive=True) - - # We have to cleanup the default dict, as default values could enable features - # which are not explicitly enabled on the CLI. Example: default-information - # originate comes with a default metric-type of 2, which will enable the - # entire default-information originate tree, even when not set via CLI so we - # need to check this first and probably drop that key. - if dict_search('default_information.originate', ospfv3) is None: - del default_values['default_information'] - if 'graceful_restart' not in ospfv3: - del default_values['graceful_restart'] - - for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'ripng', 'static']: - if dict_search(f'redistribute.{protocol}', ospfv3) is None: - del default_values['redistribute'][protocol] - if not bool(default_values['redistribute']): - del default_values['redistribute'] - - default_values.pop('interface', {}) - - # merge in remaining default values - ospfv3 = config_dict_merge(default_values, ospfv3) - - # We also need some additional information from the config, prefix-lists - # and route-maps for instance. They will be used in verify(). - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = conf.get_config_dict(['policy']) - # Merge policy dict into "regular" config dict - ospfv3 = dict_merge(tmp, ospfv3) - - return ospfv3 - -def verify(ospfv3): - if not ospfv3: - return None + ospfv3 = vrf and config_dict['vrf']['name'][vrf]['protocols']['ospfv3'] or config_dict['ospfv3'] + ospfv3['policy'] = config_dict['policy'] verify_common_route_maps(ospfv3) @@ -137,45 +82,18 @@ def verify(ospfv3): # interface is bound to our requesting VRF. Due to the VyOS # priorities the interface is bound to the VRF after creation of # the VRF itself, and before any routing protocol is configured. - if 'vrf' in ospfv3: - vrf = ospfv3['vrf'] + if vrf: tmp = get_interface_config(interface) if 'master' not in tmp or tmp['master'] != vrf: raise ConfigError(f'Interface "{interface}" is not a member of VRF "{vrf}"!') return None -def generate(ospfv3): - if not ospfv3 or 'deleted' in ospfv3: - return None - - ospfv3['new_frr_config'] = render_to_string('frr/ospf6d.frr.j2', ospfv3) - return None - -def apply(ospfv3): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - # Generate empty helper string which can be ammended to FRR commands, it - # will be either empty (default VRF) or contain the "vrf <name" statement - vrf = '' - if 'vrf' in ospfv3: - vrf = ' vrf ' + ospfv3['vrf'] - - frr_cfg.load_configuration(frr.ospf6_daemon) - frr_cfg.modify_section(f'^router ospf6{vrf}', stop_pattern='^exit', remove_stop_mark=True) - - for key in ['interface', 'interface_removed']: - if key not in ospfv3: - continue - for interface in ospfv3[key]: - frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) - - if 'new_frr_config' in ospfv3: - frr_cfg.add_before(frr.default_add_before, ospfv3['new_frr_config']) - - frr_cfg.commit_configuration(frr.ospf6_daemon) +def generate(config_dict): + frrender.generate(config_dict) +def apply(config_dict): + frrender.apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py index 994007137..3862530a2 100755 --- a/src/conf_mode/protocols_rip.py +++ b/src/conf_mode/protocols_rip.py @@ -17,58 +17,34 @@ from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.configdict import node_changed +from vyos.configdict import get_frrender_dict +from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_access_list from vyos.configverify import verify_prefix_list +from vyos.frrender import FRRender from vyos.utils.dict import dict_search -from vyos.template import render_to_string from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() + def get_config(config=None): if config: conf = config else: conf = Config() - base = ['protocols', 'rip'] - rip = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - - # FRR has VRF support for different routing daemons. As interfaces belong - # to VRFs - or the global VRF, we need to check for changed interfaces so - # that they will be properly rendered for the FRR config. Also this eases - # removal of interfaces from the running configuration. - interfaces_removed = node_changed(conf, base + ['interface']) - if interfaces_removed: - rip['interface_removed'] = list(interfaces_removed) - - # Bail out early if configuration tree does not exist - if not conf.exists(base): - rip.update({'deleted' : ''}) - return rip - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - rip = conf.merge_defaults(rip, recursive=True) - - # We also need some additional information from the config, prefix-lists - # and route-maps for instance. They will be used in verify(). - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = conf.get_config_dict(['policy']) - # Merge policy dict into "regular" config dict - rip = dict_merge(tmp, rip) - - return rip - -def verify(rip): - if not rip: + + return get_frrender_dict(conf) + +def verify(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'rip'): return None + rip = config_dict['rip'] + rip['policy'] = config_dict['policy'] + verify_common_route_maps(rip) acl_in = dict_search('distribute_list.access_list.in', rip) @@ -93,33 +69,11 @@ def verify(rip): raise ConfigError(f'You can not have "split-horizon poison-reverse" enabled ' \ f'with "split-horizon disable" for "{interface}"!') -def generate(rip): - if not rip or 'deleted' in rip: - return None - - rip['new_frr_config'] = render_to_string('frr/ripd.frr.j2', rip) - return None +def generate(frr_dict): + frrender.generate(frr_dict) def apply(rip): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - # The route-map used for the FIB (zebra) is part of the mgmt daemon as of FRR 10.1 - frr_cfg.load_configuration(frr.mgmt_daemon) - frr_cfg.modify_section('^ip protocol rip route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') - frr_cfg.modify_section('^key chain \S+', stop_pattern='^exit', remove_stop_mark=True) - frr_cfg.modify_section('^router rip', stop_pattern='^exit', remove_stop_mark=True) - - for key in ['interface', 'interface_removed']: - if key not in rip: - continue - for interface in rip[key]: - frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) - - if 'new_frr_config' in rip: - frr_cfg.add_before(frr.default_add_before, rip['new_frr_config']) - frr_cfg.commit_configuration() - + frrender.apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_ripng.py b/src/conf_mode/protocols_ripng.py index 9d4447d1f..327a0d239 100755 --- a/src/conf_mode/protocols_ripng.py +++ b/src/conf_mode/protocols_ripng.py @@ -17,48 +17,34 @@ from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge +from vyos.configdict import get_frrender_dict +from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_access_list from vyos.configverify import verify_prefix_list +from vyos.frrender import FRRender from vyos.utils.dict import dict_search -from vyos.template import render_to_string from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() + def get_config(config=None): if config: conf = config else: conf = Config() - base = ['protocols', 'ripng'] - ripng = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - - # Bail out early if configuration tree does not exist - if not conf.exists(base): - return ripng - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - ripng = conf.merge_defaults(ripng, recursive=True) - - # We also need some additional information from the config, prefix-lists - # and route-maps for instance. They will be used in verify(). - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = conf.get_config_dict(['policy']) - # Merge policy dict into "regular" config dict - ripng = dict_merge(tmp, ripng) - - return ripng - -def verify(ripng): - if not ripng: + + return get_frrender_dict(conf) + +def verify(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'ripng'): return None + ripng = config_dict['ripng'] + ripng['policy'] = config_dict['policy'] + verify_common_route_maps(ripng) acl_in = dict_search('distribute_list.access_list.in', ripng) @@ -83,25 +69,12 @@ def verify(ripng): raise ConfigError(f'You can not have "split-horizon poison-reverse" enabled ' \ f'with "split-horizon disable" for "{interface}"!') -def generate(ripng): - if not ripng: - return None - ripng['new_frr_config'] = render_to_string('frr/ripngd.frr.j2', ripng) - -def apply(ripng): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(frr.mgmt_daemon) - frr_cfg.modify_section('^ipv6 protocol ripng route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') - frr_cfg.modify_section('key chain \S+', stop_pattern='^exit', remove_stop_mark=True) - frr_cfg.modify_section('interface \S+', stop_pattern='^exit', remove_stop_mark=True) - frr_cfg.modify_section('^router ripng', stop_pattern='^exit', remove_stop_mark=True) - if 'new_frr_config' in ripng: - frr_cfg.add_before(frr.default_add_before, ripng['new_frr_config']) - frr_cfg.commit_configuration() +def generate(config_dict): + frrender.generate(config_dict) + return None +def apply(config_dict): + frrender.apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py index bec0cda91..c75f95860 100755 --- a/src/conf_mode/protocols_rpki.py +++ b/src/conf_mode/protocols_rpki.py @@ -20,15 +20,17 @@ from glob import glob from sys import exit from vyos.config import Config +from vyos.configdict import get_frrender_dict +from vyos.configverify import has_frr_protocol_in_dict +from vyos.frrender import FRRender from vyos.pki import wrap_openssh_public_key from vyos.pki import wrap_openssh_private_key -from vyos.template import render_to_string from vyos.utils.dict import dict_search_args from vyos.utils.file import write_file from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() rpki_ssh_key_base = '/run/frr/id_rpki' @@ -37,25 +39,14 @@ def get_config(config=None): conf = config else: conf = Config() - base = ['protocols', 'rpki'] + return get_frrender_dict(conf) - rpki = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, with_pki=True) - # Bail out early if configuration tree does not exist - if not conf.exists(base): - rpki.update({'deleted' : ''}) - return rpki - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - rpki = conf.merge_defaults(rpki, recursive=True) - - return rpki - -def verify(rpki): - if not rpki: +def verify(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'rpki'): return None + rpki = config_dict['rpki'] + if 'cache' in rpki: preferences = [] for peer, peer_config in rpki['cache'].items(): @@ -81,12 +72,14 @@ def verify(rpki): return None -def generate(rpki): +def generate(config_dict): for key in glob(f'{rpki_ssh_key_base}*'): os.unlink(key) - if not rpki: - return + if not has_frr_protocol_in_dict(config_dict, 'rpki'): + return None + + rpki = config_dict['rpki'] if 'cache' in rpki: for cache, cache_config in rpki['cache'].items(): @@ -102,19 +95,11 @@ def generate(rpki): write_file(cache_config['ssh']['public_key_file'], wrap_openssh_public_key(public_key_data, public_key_type)) write_file(cache_config['ssh']['private_key_file'], wrap_openssh_private_key(private_key_data)) - rpki['new_frr_config'] = render_to_string('frr/rpki.frr.j2', rpki) - + frrender.generate(config_dict) return None -def apply(rpki): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(frr.bgp_daemon) - frr_cfg.modify_section('^rpki', stop_pattern='^exit', remove_stop_mark=True) - if 'new_frr_config' in rpki: - frr_cfg.add_before(frr.default_add_before, rpki['new_frr_config']) - - frr_cfg.commit_configuration(frr.bgp_daemon) +def apply(config_dict): + frrender.apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_segment-routing.py b/src/conf_mode/protocols_segment-routing.py index 67f8005ef..0fad968e1 100755 --- a/src/conf_mode/protocols_segment-routing.py +++ b/src/conf_mode/protocols_segment-routing.py @@ -17,14 +17,17 @@ from sys import exit from vyos.config import Config -from vyos.configdict import node_changed -from vyos.template import render_to_string +from vyos.configdict import get_frrender_dict +from vyos.configdict import list_diff +from vyos.configverify import has_frr_protocol_in_dict +from vyos.frrender import FRRender +from vyos.ifconfig import Section from vyos.utils.dict import dict_search from vyos.utils.system import sysctl_write from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() def get_config(config=None): if config: @@ -32,25 +35,14 @@ def get_config(config=None): else: conf = Config() - base = ['protocols', 'segment-routing'] - sr = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True, - with_recursive_defaults=True) + return get_frrender_dict(conf) - # FRR has VRF support for different routing daemons. As interfaces belong - # to VRFs - or the global VRF, we need to check for changed interfaces so - # that they will be properly rendered for the FRR config. Also this eases - # removal of interfaces from the running configuration. - interfaces_removed = node_changed(conf, base + ['interface']) - if interfaces_removed: - sr['interface_removed'] = list(interfaces_removed) +def verify(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'segment_routing'): + return None - import pprint - pprint.pprint(sr) - return sr + sr = config_dict['segment_routing'] -def verify(sr): if 'srv6' in sr: srv6_enable = False if 'interface' in sr: @@ -62,45 +54,41 @@ def verify(sr): raise ConfigError('SRv6 should be enabled on at least one interface!') return None -def generate(sr): - if not sr: +def generate(config_dict): + frrender.generate(config_dict) + return None + +def apply(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'segment_routing'): return None - sr['new_frr_config'] = render_to_string('frr/zebra.segment_routing.frr.j2', sr) - return None + sr = config_dict['segment_routing'] -def apply(sr): - if 'interface_removed' in sr: - for interface in sr['interface_removed']: - # Disable processing of IPv6-SR packets - sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '0') + current_interfaces = Section.interfaces() + sr_interfaces = list(sr.get('interface', {}).keys()) - if 'interface' in sr: - for interface, interface_config in sr['interface'].items(): - # Accept or drop SR-enabled IPv6 packets on this interface - if 'srv6' in interface_config: - sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '1') - # Define HMAC policy for ingress SR-enabled packets on this interface - # It's a redundant check as HMAC has a default value - but better safe - # then sorry - tmp = dict_search('srv6.hmac', interface_config) - if tmp == 'accept': - sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '0') - elif tmp == 'drop': - sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '1') - elif tmp == 'ignore': - sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '-1') - else: - sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '0') + for interface in list_diff(current_interfaces, sr_interfaces): + # Disable processing of IPv6-SR packets + sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '0') - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(frr.zebra_daemon) - frr_cfg.modify_section(r'^segment-routing') - if 'new_frr_config' in sr: - frr_cfg.add_before(frr.default_add_before, sr['new_frr_config']) - frr_cfg.commit_configuration(frr.zebra_daemon) + for interface, interface_config in sr.get('interface', {}).items(): + # Accept or drop SR-enabled IPv6 packets on this interface + if 'srv6' in interface_config: + sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '1') + # Define HMAC policy for ingress SR-enabled packets on this interface + # It's a redundant check as HMAC has a default value - but better safe + # then sorry + tmp = dict_search('srv6.hmac', interface_config) + if tmp == 'accept': + sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '0') + elif tmp == 'drop': + sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '1') + elif tmp == 'ignore': + sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '-1') + else: + sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '0') + frrender.apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py index c5dc77fd2..1e498b256 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -14,21 +14,25 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +from ipaddress import IPv4Network from sys import exit from sys import argv from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.configdict import get_dhcp_interfaces -from vyos.configdict import get_pppoe_interfaces +from vyos.configdict import get_frrender_dict +from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_vrf +from vyos.frrender import FRRender from vyos.template import render -from vyos.template import render_to_string from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() + +vrf = None +if len(argv) > 1: + vrf = argv[1] config_file = '/etc/iproute2/rt_tables.d/vyos-static.conf' @@ -38,36 +42,17 @@ def get_config(config=None): else: conf = Config() - vrf = None - if len(argv) > 1: - vrf = argv[1] + return get_frrender_dict(conf) + +def verify(config_dict): + global vrf + if not has_frr_protocol_in_dict(config_dict, 'static', vrf): + return None - base_path = ['protocols', 'static'] # eqivalent of the C foo ? 'a' : 'b' statement - base = vrf and ['vrf', 'name', vrf, 'protocols', 'static'] or base_path - static = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) - - # Assign the name of our VRF context - if vrf: static['vrf'] = vrf - - # We also need some additional information from the config, prefix-lists - # and route-maps for instance. They will be used in verify(). - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = conf.get_config_dict(['policy']) - # Merge policy dict into "regular" config dict - static = dict_merge(tmp, static) - - # T3680 - get a list of all interfaces currently configured to use DHCP - tmp = get_dhcp_interfaces(conf, vrf) - if tmp: static.update({'dhcp' : tmp}) - tmp = get_pppoe_interfaces(conf, vrf) - if tmp: static.update({'pppoe' : tmp}) - - return static - -def verify(static): + static = vrf and config_dict['vrf']['name'][vrf]['protocols']['static'] or config_dict['static'] + static['policy'] = config_dict['policy'] + verify_common_route_maps(static) for route in ['route', 'route6']: @@ -90,33 +75,28 @@ def verify(static): raise ConfigError(f'Can not use both blackhole and reject for '\ f'prefix "{prefix}"!') + if 'multicast' in static and 'route' in static['multicast']: + for prefix, prefix_options in static['multicast']['route'].items(): + if not IPv4Network(prefix).is_multicast: + raise ConfigError(f'{prefix} is not a multicast network!') + return None -def generate(static): - if not static: +def generate(config_dict): + global vrf + if not has_frr_protocol_in_dict(config_dict, 'static', vrf): return None + # eqivalent of the C foo ? 'a' : 'b' statement + static = vrf and config_dict['vrf']['name'][vrf]['protocols']['static'] or config_dict['static'] + # Put routing table names in /etc/iproute2/rt_tables render(config_file, 'iproute2/static.conf.j2', static) - static['new_frr_config'] = render_to_string('frr/staticd.frr.j2', static) + frrender.generate(config_dict) return None def apply(static): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(frr.mgmt_daemon) - - if 'vrf' in static: - vrf = static['vrf'] - frr_cfg.modify_section(f'^vrf {vrf}', stop_pattern='^exit-vrf', remove_stop_mark=True) - else: - frr_cfg.modify_section(r'^ip route .*') - frr_cfg.modify_section(r'^ipv6 route .*') - - if 'new_frr_config' in static: - frr_cfg.add_before(frr.default_add_before, static['new_frr_config']) - frr_cfg.commit_configuration() - + frrender.apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_static_multicast.py b/src/conf_mode/protocols_static_multicast.py deleted file mode 100755 index 4393f3ed3..000000000 --- a/src/conf_mode/protocols_static_multicast.py +++ /dev/null @@ -1,133 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2020-2024 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - - -from ipaddress import IPv4Address -from sys import exit - -from vyos import ConfigError -from vyos import frr -from vyos.config import Config -from vyos.template import render_to_string - -from vyos import airbag -airbag.enable() - -config_file = r'/tmp/static_mcast.frr' - -# Get configuration for static multicast route -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - mroute = { - 'old_mroute' : {}, - 'mroute' : {} - } - - base_path = "protocols static multicast" - - if not (conf.exists(base_path) or conf.exists_effective(base_path)): - return None - - conf.set_level(base_path) - - # Get multicast effective routes - for route in conf.list_effective_nodes('route'): - mroute['old_mroute'][route] = {} - for next_hop in conf.list_effective_nodes('route {0} next-hop'.format(route)): - mroute['old_mroute'][route].update({ - next_hop : conf.return_value('route {0} next-hop {1} distance'.format(route, next_hop)) - }) - - # Get multicast effective interface-routes - for route in conf.list_effective_nodes('interface-route'): - if not route in mroute['old_mroute']: - mroute['old_mroute'][route] = {} - for next_hop in conf.list_effective_nodes('interface-route {0} next-hop-interface'.format(route)): - mroute['old_mroute'][route].update({ - next_hop : conf.return_value('interface-route {0} next-hop-interface {1} distance'.format(route, next_hop)) - }) - - # Get multicast routes - for route in conf.list_nodes('route'): - mroute['mroute'][route] = {} - for next_hop in conf.list_nodes('route {0} next-hop'.format(route)): - mroute['mroute'][route].update({ - next_hop : conf.return_value('route {0} next-hop {1} distance'.format(route, next_hop)) - }) - - # Get multicast interface-routes - for route in conf.list_nodes('interface-route'): - if not route in mroute['mroute']: - mroute['mroute'][route] = {} - for next_hop in conf.list_nodes('interface-route {0} next-hop-interface'.format(route)): - mroute['mroute'][route].update({ - next_hop : conf.return_value('interface-route {0} next-hop-interface {1} distance'.format(route, next_hop)) - }) - - return mroute - -def verify(mroute): - if mroute is None: - return None - - for mcast_route in mroute['mroute']: - route = mcast_route.split('/') - if IPv4Address(route[0]) < IPv4Address('224.0.0.0'): - raise ConfigError(f'{mcast_route} not a multicast network') - - -def generate(mroute): - if mroute is None: - return None - - mroute['new_frr_config'] = render_to_string('frr/static_mcast.frr.j2', mroute) - return None - - -def apply(mroute): - if mroute is None: - return None - - frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(frr.mgmt_daemon) - - if 'old_mroute' in mroute: - for route_gr in mroute['old_mroute']: - for nh in mroute['old_mroute'][route_gr]: - if mroute['old_mroute'][route_gr][nh]: - frr_cfg.modify_section(f'^ip mroute {route_gr} {nh} {mroute["old_mroute"][route_gr][nh]}') - else: - frr_cfg.modify_section(f'^ip mroute {route_gr} {nh}') - - if 'new_frr_config' in mroute: - frr_cfg.add_before(frr.default_add_before, mroute['new_frr_config']) - - frr_cfg.commit_configuration() - return None - - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/service_snmp.py b/src/conf_mode/service_snmp.py index 134662f85..1174b1238 100755 --- a/src/conf_mode/service_snmp.py +++ b/src/conf_mode/service_snmp.py @@ -34,7 +34,6 @@ from vyos.utils.process import call from vyos.utils.permission import chmod_755 from vyos.version import get_version_data from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() @@ -261,16 +260,6 @@ def apply(snmp): # start SNMP daemon call(f'systemctl reload-or-restart {systemd_service}') - - # Enable AgentX in FRR - # This should be done for each daemon individually because common command - # works only if all the daemons started with SNMP support - # Following daemons from FRR 9.0/stable have SNMP module compiled in VyOS - frr_daemons_list = [frr.zebra_daemon, frr.bgp_daemon, frr.ospf_daemon, frr.ospf6_daemon, - frr.rip_daemon, frr.isis_daemon, frr.ldpd_daemon] - for frr_daemon in frr_daemons_list: - call(f'vtysh -c "configure terminal" -d {frr_daemon} -c "agentx" >/dev/null') - return None if __name__ == '__main__': diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 3d3845fdf..a13bb8b1e 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -19,13 +19,14 @@ from jmespath import search from json import loads from vyos.config import Config +from vyos.configdict import get_frrender_dict from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.configverify import verify_route_map from vyos.firewall import conntrack_required +from vyos.frrender import FRRender from vyos.ifconfig import Interface from vyos.template import render -from vyos.template import render_to_string from vyos.utils.dict import dict_search from vyos.utils.network import get_vrf_tableid from vyos.utils.network import get_vrf_members @@ -35,9 +36,9 @@ from vyos.utils.process import cmd from vyos.utils.process import popen from vyos.utils.system import sysctl_write from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() config_file = '/etc/iproute2/rt_tables.d/vyos-vrf.conf' k_mod = ['vrf'] @@ -132,6 +133,10 @@ def get_config(config=None): if 'name' in vrf: vrf['conntrack'] = conntrack_required(conf) + # We need to merge the FRR rendering dict into the VRF dict + # this is required to get the route-map information to FRR + vrf.update({'frrender' : get_frrender_dict(conf)}) + # We also need the route-map information from the config # # XXX: one MUST always call this without the key_mangling() option! See @@ -204,8 +209,9 @@ def verify(vrf): def generate(vrf): # Render iproute2 VR helper names render(config_file, 'iproute2/vrf.conf.j2', vrf) - # Render VRF Kernel/Zebra route-map filters - vrf['frr_zebra_config'] = render_to_string('frr/zebra.vrf.route-map.frr.j2', vrf) + + if 'frrender' in vrf: + frrender.generate(vrf['frrender']) return None @@ -347,13 +353,8 @@ def apply(vrf): if has_rule(afi, 2000, 'l3mdev'): call(f'ip {afi} rule del pref 2000 l3mdev unreachable') - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(frr.mgmt_daemon) - frr_cfg.modify_section(f'^vrf .+', stop_pattern='^exit-vrf', remove_stop_mark=True) - if 'frr_zebra_config' in vrf: - frr_cfg.add_before(frr.default_add_before, vrf['frr_zebra_config']) - frr_cfg.commit_configuration() + if 'frrender' in vrf: + frrender.apply() return None |