From f04c0f59699095e3b6708f16cc01222c273d41ac Mon Sep 17 00:00:00 2001 From: Cheeze_It Date: Sun, 22 Nov 2020 21:01:40 -0700 Subject: mpls-conf: T915: Refactored FRR LDP template, MPLS handler, added MPLS global features So this is a big update. The first thing that was done was a refactor to the FRR LDP template, MPLS handler, and XML conf tree MPLS global additions. The refactors should work and I did test them in my lab. It seems that everything does work as needed so far in my testing. There is something here that is considered configuration breaking from the old setup though. In the old setup the MPLS interface operation (as in the interfaces accepting MPLS labels and processing them) was tied with LDP. What this means is that MPLS processing was enabled at the same time as LDP interfaces were configured. We do not want this behavior for the future as there's other MPLS underlay technologies like SR and RSVP. If someone wants to enable SR or RSVP without enabling LDP then they now can. Before, they couldn't. The other additions are global changes to MPLS TTL propagation and MPLS max TTL enforcement. They have now been added. Lastly, there is an frr-reload bug that Runar Borge found with this. We have found that when totally deleting LDP that there has to be 3 commits done. This is because frr-reload doesn't properly do what it needs to do in 1 operation so we had to do 3. This will only affect people that are doing an entire LDP clear using "delete protocols mpls ldp." Otherwise it isn't seen. Anyway, this refactor now works with the FRR daemon directly for all changes. This also makes it much easier for adding stuff in the future. Thank you --- src/conf_mode/protocols_mpls.py | 411 ++++++++++------------------------------ 1 file changed, 95 insertions(+), 316 deletions(-) (limited to 'src/conf_mode/protocols_mpls.py') diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index 84948baf4..6d3b98c47 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) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2017-2020 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 @@ -16,351 +16,130 @@ import os +from sys import exit + from vyos.config import Config +from vyos.configdict import node_changed from vyos import ConfigError from vyos.util import call from vyos.template import render - +from vyos.template import render_to_string +from vyos import frr from vyos import airbag airbag.enable() config_file = r'/tmp/ldpd.frr' -def sysctl(name, value): - call('sysctl -wq {}={}'.format(name, value)) - def get_config(config=None): if config: conf = config else: conf = Config() - mpls_conf = { - 'router_id' : None, - 'mpls_ldp' : False, - 'old_parameters' : { - 'no_ttl_propagation' : False, - 'maximum_ttl' : None - }, - 'parameters' : { - 'no_ttl_propagation' : False, - 'maximum_ttl' : None - }, - 'old_ldp' : { - 'interfaces' : [], - 'neighbors' : {}, - 'd_transp_ipv4' : None, - 'd_transp_ipv6' : None, - 'hello_ipv4_holdtime' : None, - 'hello_ipv4_interval' : None, - 'hello_ipv6_holdtime' : None, - 'hello_ipv6_interval' : None, - 'ses_ipv4_hold' : None, - 'ses_ipv6_hold' : None, - 'export_ipv4_exp' : False, - 'export_ipv6_exp' : False, - 'cisco_interop_tlv' : False, - 'transport_prefer_ipv4' : False, - 'target_ipv4_addresses' : [], - 'target_ipv6_addresses' : [], - 'target_ipv4_enable' : False, - 'target_ipv6_enable' : False, - 'target_ipv4_hello_int' : None, - 'target_ipv6_hello_int' : None, - 'target_ipv4_hello_hold' : None, - 'target_ipv6_hello_hold' : None - }, - 'ldp' : { - 'interfaces' : [], - 'neighbors' : {}, - 'd_transp_ipv4' : None, - 'd_transp_ipv6' : None, - 'hello_ipv4_holdtime' : None, - 'hello_ipv4_interval' : None, - 'hello_ipv6_holdtime' : None, - 'hello_ipv6_interval' : None, - 'ses_ipv4_hold' : None, - 'ses_ipv6_hold' : None, - 'export_ipv4_exp' : False, - 'export_ipv6_exp' : False, - 'cisco_interop_tlv' : False, - 'transport_prefer_ipv4' : False, - 'target_ipv4_addresses' : [], - 'target_ipv6_addresses' : [], - 'target_ipv4_enable' : False, - 'target_ipv6_enable' : False, - 'target_ipv4_hello_int' : None, - 'target_ipv6_hello_int' : None, - 'target_ipv4_hello_hold' : None, - 'target_ipv6_hello_hold' : None - } - } - if not (conf.exists('protocols mpls') or conf.exists_effective('protocols mpls')): - return None - - # If LDP is defined then enable LDP portion of code - if conf.exists('protocols mpls ldp'): - mpls_conf['mpls_ldp'] = True - - # Set to MPLS hierarchy configuration level - conf.set_level('protocols mpls') - - # Get no_ttl_propagation - if conf.exists_effective('parameters no-propagate-ttl'): - mpls_conf['old_parameters']['no_ttl_propagation'] = True - - if conf.exists('parameters no-propagate-ttl'): - mpls_conf['parameters']['no_ttl_propagation'] = True - - # Get maximum_ttl - if conf.exists_effective('parameters maximum-ttl'): - mpls_conf['old_parameters']['maximum_ttl'] = conf.return_effective_value('parameters maximum-ttl') - - if conf.exists('parameters maximum-ttl'): - mpls_conf['parameters']['maximum_ttl'] = conf.return_value('parameters maximum-ttl') - - # Set to LDP hierarchy configuration level - conf.set_level('protocols mpls ldp') - - # Get router-id - if conf.exists_effective('router-id'): - mpls_conf['old_router_id'] = conf.return_effective_value('router-id') - - if conf.exists('router-id'): - mpls_conf['router_id'] = conf.return_value('router-id') - - # Get hello-ipv4-holdtime - if conf.exists_effective('discovery hello-ipv4-holdtime'): - mpls_conf['old_ldp']['hello_ipv4_holdtime'] = conf.return_effective_value('discovery hello-ipv4-holdtime') - - if conf.exists('discovery hello-ipv4-holdtime'): - mpls_conf['ldp']['hello_ipv4_holdtime'] = conf.return_value('discovery hello-ipv4-holdtime') - - # Get hello-ipv4-interval - if conf.exists_effective('discovery hello-ipv4-interval'): - mpls_conf['old_ldp']['hello_ipv4_interval'] = conf.return_effective_value('discovery hello-ipv4-interval') - - if conf.exists('discovery hello-ipv4-interval'): - mpls_conf['ldp']['hello_ipv4_interval'] = conf.return_value('discovery hello-ipv4-interval') - - # Get hello-ipv6-holdtime - if conf.exists_effective('discovery hello-ipv6-holdtime'): - mpls_conf['old_ldp']['hello_ipv6_holdtime'] = conf.return_effective_value('discovery hello-ipv6-holdtime') - - if conf.exists('discovery hello-ipv6-holdtime'): - mpls_conf['ldp']['hello_ipv6_holdtime'] = conf.return_value('discovery hello-ipv6-holdtime') - - # Get hello-ipv6-interval - if conf.exists_effective('discovery hello-ipv6-interval'): - mpls_conf['old_ldp']['hello_ipv6_interval'] = conf.return_effective_value('discovery hello-ipv6-interval') - - if conf.exists('discovery hello-ipv6-interval'): - mpls_conf['ldp']['hello_ipv6_interval'] = conf.return_value('discovery hello-ipv6-interval') - - # Get session-ipv4-holdtime - if conf.exists_effective('discovery session-ipv4-holdtime'): - mpls_conf['old_ldp']['ses_ipv4_hold'] = conf.return_effective_value('discovery session-ipv4-holdtime') - - if conf.exists('discovery session-ipv4-holdtime'): - mpls_conf['ldp']['ses_ipv4_hold'] = conf.return_value('discovery session-ipv4-holdtime') - - # Get session-ipv6-holdtime - if conf.exists_effective('discovery session-ipv6-holdtime'): - mpls_conf['old_ldp']['ses_ipv6_hold'] = conf.return_effective_value('discovery session-ipv6-holdtime') - - if conf.exists('discovery session-ipv6-holdtime'): - mpls_conf['ldp']['ses_ipv6_hold'] = conf.return_value('discovery session-ipv6-holdtime') - - # Get discovery transport-ipv4-address - if conf.exists_effective('discovery transport-ipv4-address'): - mpls_conf['old_ldp']['d_transp_ipv4'] = conf.return_effective_value('discovery transport-ipv4-address') - - if conf.exists('discovery transport-ipv4-address'): - mpls_conf['ldp']['d_transp_ipv4'] = conf.return_value('discovery transport-ipv4-address') - - # Get discovery transport-ipv6-address - if conf.exists_effective('discovery transport-ipv6-address'): - mpls_conf['old_ldp']['d_transp_ipv6'] = conf.return_effective_value('discovery transport-ipv6-address') - - if conf.exists('discovery transport-ipv6-address'): - mpls_conf['ldp']['d_transp_ipv6'] = conf.return_value('discovery transport-ipv6-address') - - # Get export ipv4 explicit-null - if conf.exists_effective('export ipv4 explicit-null'): - mpls_conf['old_ldp']['export_ipv4_exp'] = True - - if conf.exists('export ipv4 explicit-null'): - mpls_conf['ldp']['export_ipv4_exp'] = True - - # Get export ipv6 explicit-null - if conf.exists_effective('export ipv6 explicit-null'): - mpls_conf['old_ldp']['export_ipv6_exp'] = True - - if conf.exists('export ipv6 explicit-null'): - mpls_conf['ldp']['export_ipv6_exp'] = True - - # Get target_ipv4_addresses - if conf.exists_effective('targeted-neighbor ipv4 address'): - mpls_conf['old_ldp']['target_ipv4_addresses'] = conf.return_effective_values('targeted-neighbor ipv4 address') - - if conf.exists('targeted-neighbor ipv4 address'): - mpls_conf['ldp']['target_ipv4_addresses'] = conf.return_values('targeted-neighbor ipv4 address') - - # Get target_ipv4_enable - if conf.exists_effective('targeted-neighbor ipv4 enable'): - mpls_conf['old_ldp']['target_ipv4_enable'] = True - - if conf.exists('targeted-neighbor ipv4 enable'): - mpls_conf['ldp']['target_ipv4_enable'] = True - - # Get target_ipv4_hello_int - if conf.exists_effective('targeted-neighbor ipv4 hello-interval'): - mpls_conf['old_ldp']['target_ipv4_hello_int'] = conf.return_effective_value('targeted-neighbor ipv4 hello-interval') - - if conf.exists('targeted-neighbor ipv4 hello-interval'): - mpls_conf['ldp']['target_ipv4_hello_int'] = conf.return_value('targeted-neighbor ipv4 hello-interval') - - # Get target_ipv4_hello_hold - if conf.exists_effective('targeted-neighbor ipv4 hello-holdtime'): - mpls_conf['old_ldp']['target_ipv4_hello_hold'] = conf.return_effective_value('targeted-neighbor ipv4 hello-holdtime') - - if conf.exists('targeted-neighbor ipv4 hello-holdtime'): - mpls_conf['ldp']['target_ipv4_hello_hold'] = conf.return_value('targeted-neighbor ipv4 hello-holdtime') - - # Get target_ipv6_addresses - if conf.exists_effective('targeted-neighbor ipv6 address'): - mpls_conf['old_ldp']['target_ipv6_addresses'] = conf.return_effective_values('targeted-neighbor ipv6 address') - - if conf.exists('targeted-neighbor ipv6 address'): - mpls_conf['ldp']['target_ipv6_addresses'] = conf.return_values('targeted-neighbor ipv6 address') - - # Get target_ipv6_enable - if conf.exists_effective('targeted-neighbor ipv6 enable'): - mpls_conf['old_ldp']['target_ipv6_enable'] = True - - if conf.exists('targeted-neighbor ipv6 enable'): - mpls_conf['ldp']['target_ipv6_enable'] = True - - # Get target_ipv6_hello_int - if conf.exists_effective('targeted-neighbor ipv6 hello-interval'): - mpls_conf['old_ldp']['target_ipv6_hello_int'] = conf.return_effective_value('targeted-neighbor ipv6 hello-interval') - - if conf.exists('targeted-neighbor ipv6 hello-interval'): - mpls_conf['ldp']['target_ipv6_hello_int'] = conf.return_value('targeted-neighbor ipv6 hello-interval') - - # Get target_ipv6_hello_hold - if conf.exists_effective('targeted-neighbor ipv6 hello-holdtime'): - mpls_conf['old_ldp']['target_ipv6_hello_hold'] = conf.return_effective_value('targeted-neighbor ipv6 hello-holdtime') - - if conf.exists('targeted-neighbor ipv6 hello-holdtime'): - mpls_conf['ldp']['target_ipv6_hello_hold'] = conf.return_value('targeted-neighbor ipv6 hello-holdtime') - - # Get parameters cisco-interop-tlv - if conf.exists_effective('parameters cisco-interop-tlv'): - mpls_conf['old_ldp']['cisco_interop_tlv'] = True - - if conf.exists('parameters cisco-interop-tlv'): - mpls_conf['ldp']['cisco_interop_tlv'] = True - - # Get parameters transport-prefer-ipv4 - if conf.exists_effective('parameters transport-prefer-ipv4'): - mpls_conf['old_ldp']['transport_prefer_ipv4'] = True - - if conf.exists('parameters transport-prefer-ipv4'): - mpls_conf['ldp']['transport_prefer_ipv4'] = True - - # Get interfaces - if conf.exists_effective('interface'): - mpls_conf['old_ldp']['interfaces'] = conf.return_effective_values('interface') - - if conf.exists('interface'): - mpls_conf['ldp']['interfaces'] = conf.return_values('interface') - - # Get neighbors - for neighbor in conf.list_effective_nodes('neighbor'): - mpls_conf['old_ldp']['neighbors'].update({ - neighbor : { - 'password' : conf.return_effective_value('neighbor {0} password'.format(neighbor), default=''), - 'ttl_security' : conf.return_effective_value('neighbor {0} ttl-security'.format(neighbor), default=''), - 'session_holdtime' : conf.return_effective_value('neighbor {0} session-holdtime'.format(neighbor), default='') - } - }) - - for neighbor in conf.list_nodes('neighbor'): - mpls_conf['ldp']['neighbors'].update({ - neighbor : { - 'password' : conf.return_value('neighbor {0} password'.format(neighbor), default=''), - 'ttl_security' : conf.return_value('neighbor {0} ttl-security'.format(neighbor), default=''), - 'session_holdtime' : conf.return_value('neighbor {0} session-holdtime'.format(neighbor), default='') - } - }) - - return mpls_conf - -def operate_mpls_on_intfc(interfaces, action): - rp_filter = 0 - if action == 1: - rp_filter = 2 - for iface in interfaces: - sysctl('net.mpls.conf.{0}.input'.format(iface), action) - # Operate rp filter - sysctl('net.ipv4.conf.{0}.rp_filter'.format(iface), rp_filter) + base = ['protocols', 'mpls'] + mpls = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + return mpls def verify(mpls): - if mpls is None: + # If no config, then just bail out early. + if not mpls: return None - if mpls['mpls_ldp']: - # Require router-id - if not mpls['router_id']: - raise ConfigError(f"MPLS ldp router-id is mandatory!") - - # Require discovery transport-address - if not mpls['ldp']['d_transp_ipv4'] and not mpls['ldp']['d_transp_ipv6']: - raise ConfigError(f"MPLS ldp discovery transport address is mandatory!") + # Checks to see if LDP is properly configured + if 'ldp' in mpls: + # If router ID not defined + if 'router_id' in mpls['ldp']: + pass + else: + raise ConfigError('Router ID missing. An LDP router id is mandatory!') + # If interface not set + if 'interface' in mpls['ldp']: + pass + else: + raise ConfigError('LDP interfaces are missing. An LDP interface is mandatory!') + # If transport addresses are not set + if 'discovery' in mpls['ldp']: + if 'transport_ipv4_address' in mpls['ldp']['discovery']: + pass + elif 'transport_ipv6_address' in mpls['ldp']['discovery']: + pass + else: + raise ConfigError('LDP transport address is missing. Add an LDP transport address!') + else: + raise ConfigError('LDP transport address is missing. Add an LDP transport address!') - # Require interface - if not mpls['ldp']['interfaces']: - raise ConfigError(f"MPLS ldp interface is mandatory!") + return None def generate(mpls): - if mpls is None: + # If there's no MPLS config generated, create dictionary key with no value. + if not mpls: + mpls['new_frr_config'] = '' return None - render(config_file, 'frr/ldpd.frr.tmpl', mpls) + mpls['new_frr_config'] = render_to_string('frr/ldpd.frr.tmpl', mpls, trim_blocks=True) return None def apply(mpls): - if mpls is None: - return None - - # Set number of entries in the platform label table - if mpls['mpls_ldp']: - sysctl('net.mpls.platform_labels', '1048575') + # Define dictionary that will load FRR config + frr_cfg = {} + # Save original configuration prior to starting any commit actions + frr_cfg['original_config'] = frr.get_configuration(daemon='ldpd') + frr_cfg['modified_config'] = frr.replace_section(frr_cfg['original_config'], mpls['new_frr_config'], from_re='mpls.*') + + # If FRR config is blank, rerun the blank commit three times due to frr-reload + # behavior/bug not properly clearing out on one commit. + if mpls['new_frr_config'] == '': + for x in range(3): + frr.reload_configuration(frr_cfg['modified_config'], daemon='ldpd') + elif not 'ldp' in mpls: + for x in range(3): + frr.reload_configuration(frr_cfg['modified_config'], daemon='ldpd') else: - sysctl('net.mpls.platform_labels', '0') + # FRR mark configuration will test for syntax errors and throws an + # exception if any syntax errors is detected + frr.mark_configuration(frr_cfg['modified_config']) + + # Commit resulting configuration to FRR, this will throw CommitError + # on failure + frr.reload_configuration(frr_cfg['modified_config'], daemon='ldpd') - # Choose whether to copy IP TTL to MPLS header TTL - if mpls['parameters']['no_ttl_propagation']: - sysctl('net.mpls.ip_ttl_propagate', '0') + # Set number of entries in the platform label tables + if 'interface' in mpls: + os.system('sysctl -wq net.mpls.platform_labels=1048575') else: - sysctl('net.mpls.ip_ttl_propagate', '1') - - # Choose whether to limit maximum MPLS header TTL - if mpls['parameters']['maximum_ttl']: - sysctl('net.mpls.default_ttl', '%s' %(mpls['parameters']['maximum_ttl'])) + os.system('sysctl -wq net.mpls.platform_labels=0') + + # Check for changes in global MPLS options + if 'parameters' in mpls: + # Choose whether to copy IP TTL to MPLS header TTL + if 'no_propagate_ttl' in mpls['parameters']: + os.system('sysctl -wq net.mpls.ip_ttl_propagate=0') + # Choose whether to limit maximum MPLS header TTL + if 'maximum_ttl' in mpls['parameters']: + os.system('sysctl -wq net.mpls.default_ttl=%s' %(mpls['parameters'].get('maximum_ttl'))) else: - sysctl('net.mpls.default_ttl', '255') - - # Allow mpls on interfaces - operate_mpls_on_intfc(mpls['ldp']['interfaces'], 1) - - # Disable mpls on deleted interfaces - diactive_ifaces = set(mpls['old_ldp']['interfaces']).difference(mpls['ldp']['interfaces']) - operate_mpls_on_intfc(diactive_ifaces, 0) - - if os.path.exists(config_file): - call(f'vtysh -d ldpd -f {config_file}') - os.remove(config_file) + # Set default global MPLS options if not defined. + os.system('sysctl -wq net.mpls.ip_ttl_propagate=1') + os.system('sysctl -wq net.mpls.default_ttl=255') + + # Enable and disable MPLS processing on interfaces per configuration + if 'interface' in mpls: + system_interfaces = [] + system_interfaces.append(((os.popen('sysctl net.mpls.conf').read()).split('\n'))) + del system_interfaces[0][-1] + for configured_interface in mpls['interface']: + for system_interface in system_interfaces[0]: + if configured_interface in system_interface: + os.system('sysctl -wq net.mpls.conf.%s.input=1' %(configured_interface)) + elif system_interface.endswith(' = 1'): + system_interface = system_interface.replace(' = 1', '=0') + os.system('sysctl -wq %s' %(system_interface)) + else: + # If MPLS interfaces are not configured, set MPLS processing disabled + system_interfaces = [] + system_interfaces.append(((os.popen('sysctl net.mpls.conf').read()).replace(" = 1", "=0")).split('\n')) + del system_interfaces[0][-1] + for interface in (system_interfaces[0]): + os.system('sysctl -wq %s' %(interface)) return None -- cgit v1.2.3