From d072b88834fa3e7cb9d1f07d672c2da5f3825a24 Mon Sep 17 00:00:00 2001 From: aapostoliuk Date: Thu, 22 Dec 2022 17:52:12 +0200 Subject: T4877: Added more checks if "import vrf" is used in bgp 1. Fixed: If rd and route-target are used in VRF, can not use "import vrf" in the same VRF in the same AFI/SAFI. 2. Fixed: If rd and route-target is used in VRF, this VRF can not be in the list of command "import vrf" in the same AFI/SAFI but in other VRFs. 3. Fixed: Do not allow to delete vrf if it is used in import list of other vrfs. 4. Added smoketests to check "import vrf" issues. --- src/conf_mode/protocols_bgp.py | 164 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 146 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index ff568d470..c410258ee 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -14,8 +14,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os - from sys import exit from sys import argv @@ -57,13 +55,18 @@ def get_config(config=None): # instead of the VRF instance. if vrf: bgp.update({'vrf' : vrf}) + bgp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + 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' : ''}) - if not vrf: - # We are running in the default VRF context, thus we can not delete - # our main BGP instance if there are dependent BGP VRF instances. - bgp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'], - key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) return bgp # We also need some additional information from the config, prefix-lists @@ -74,9 +77,91 @@ def get_config(config=None): tmp = conf.get_config_dict(['policy']) # Merge policy dict into "regular" config dict bgp = dict_merge(tmp, bgp) - return bgp + +def verify_vrf_as_import(search_vrf_name: str, afi_name: str, vrfs_config: dict) -> bool: + """ + :param search_vrf_name: search vrf name in import list + :type search_vrf_name: str + :param afi_name: afi/safi name + :type afi_name: str + :param vrfs_config: configuration dependents vrfs + :type vrfs_config: dict + :return: if vrf in import list retrun true else false + :rtype: bool + """ + for vrf_name, vrf_config in vrfs_config.items(): + import_list = dict_search( + f'protocols.bgp.address_family.{afi_name}.import.vrf', + vrf_config) + if import_list: + if search_vrf_name in import_list: + return True + return False + +def verify_vrf_import_options(afi_config: dict) -> bool: + """ + Search if afi contains one of options + :param afi_config: afi/safi + :type afi_config: dict + :return: if vrf contains rd and route-target options return true else false + :rtype: bool + """ + options = [ + f'rd.vpn.export', + f'route_target.vpn.import', + f'route_target.vpn.export', + f'route_target.vpn.both' + ] + for option in options: + if dict_search(option, afi_config): + return True + return False + +def verify_vrf_import(vrf_name: str, vrfs_config: dict, afi_name: str) -> bool: + """ + Verify if vrf exists and contain options + :param vrf_name: name of VRF + :type vrf_name: str + :param vrfs_config: dependent vrfs config + :type vrfs_config: dict + :param afi_name: afi/safi name + :type afi_name: str + :return: if vrf contains rd and route-target options return true else false + :rtype: bool + """ + if vrf_name != 'default': + verify_vrf({'vrf': vrf_name}) + if dict_search(f'{vrf_name}.protocols.bgp.address_family.{afi_name}', + vrfs_config): + afi_config = \ + vrfs_config[vrf_name]['protocols']['bgp']['address_family'][ + afi_name] + if verify_vrf_import_options(afi_config): + return True + return False + +def verify_vrflist_import(afi_name: str, afi_config: dict, vrfs_config: dict) -> bool: + """ + Call function to verify + if scpecific vrf contains rd and route-target + options return true else false + + :param afi_name: afi/safi name + :type afi_name: str + :param afi_config: afi/safi configuration + :type afi_config: dict + :param vrfs_config: dependent vrfs config + :type vrfs_config:dict + :return: if vrf contains rd and route-target options return true else false + :rtype: bool + """ + for vrf_name in afi_config['import']['vrf']: + if verify_vrf_import(vrf_name, vrfs_config, afi_name): + return True + return False + def verify_remote_as(peer_config, bgp_config): if 'remote_as' in peer_config: return peer_config['remote_as'] @@ -113,12 +198,22 @@ def verify_afi(peer_config, bgp_config): return False def verify(bgp): - if not bgp or 'deleted' in bgp: - if 'dependent_vrfs' in bgp: - for vrf, vrf_options in bgp['dependent_vrfs'].items(): - if dict_search('protocols.bgp', vrf_options) != None: - raise ConfigError('Cannot delete default BGP instance, ' \ - 'dependent VRF instance(s) exist!') + if 'deleted' in bgp: + if 'vrf' in bgp: + # 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 {bgp["vrf"]} instance, ' \ + 'Please unconfigure import vrf commands!') + else: + # We are running in the default VRF context, thus we can not delete + # our main BGP instance if there are dependent BGP VRF instances. + if 'dependent_vrfs' in 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!') return None if 'system_as' not in bgp: @@ -324,9 +419,43 @@ def verify(bgp): f'{afi} administrative distance {key}!') if afi in ['ipv4_unicast', 'ipv6_unicast']: - if 'import' in afi_config and 'vrf' in afi_config['import']: - # Check if VRF exists - verify_vrf(afi_config['import']['vrf']) + + vrf_name = bgp['vrf'] if dict_search('vrf', bgp) 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): + if verify_vrf_as_import(vrf_name, afi, bgp['dependent_vrfs']): + raise ConfigError( + 'Command "import vrf" conflicts with "rd vpn export" command!') + + if dict_search('route_target.vpn.both', afi_config): + if verify_vrf_as_import(vrf_name, afi, bgp['dependent_vrfs']): + raise ConfigError( + 'Command "import vrf" conflicts with "route-target vpn both" command!') + + if dict_search('route_target.vpn.import', afi_config): + if verify_vrf_as_import(vrf_name, afi, bgp['dependent_vrfs']): + raise ConfigError( + 'Command "import vrf conflicts" with "route-target vpn import" command!') + + if dict_search('route_target.vpn.export', afi_config): + if verify_vrf_as_import(vrf_name, afi, bgp['dependent_vrfs']): + raise ConfigError( + 'Command "import vrf" conflicts with "route-target vpn export" command!') + + # Verify if VRFs in import do not contain rd + # and route-target options + if dict_search('import.vrf', afi_config) is not None: + # Verify if VRF with import does not contain rd + # and route-target options + if verify_vrf_import_options(afi_config): + raise ConfigError( + 'Please unconfigure "import vrf" commands before using vpn commands in the same VRF!') + # Verify if VRFs in import list do not contain rd + # and route-target options + if verify_vrflist_import(afi, afi_config, bgp['dependent_vrfs']): + raise ConfigError( + 'Please unconfigure import vrf commands before using vpn commands in dependent VRFs!') # FRR error: please unconfigure vpn to vrf commands before # using import vrf commands @@ -339,7 +468,6 @@ def verify(bgp): tmp = dict_search(f'route_map.vpn.{export_import}', afi_config) if tmp: verify_route_map(tmp, bgp) - return None def generate(bgp): -- cgit v1.2.3