diff options
Diffstat (limited to 'python/vyos')
-rw-r--r-- | python/vyos/config.py | 1 | ||||
-rw-r--r-- | python/vyos/configdep.py | 49 | ||||
-rw-r--r-- | python/vyos/configtree.py | 9 | ||||
-rw-r--r-- | python/vyos/defaults.py | 10 | ||||
-rw-r--r-- | python/vyos/firewall.py | 22 | ||||
-rw-r--r-- | python/vyos/ifconfig/interface.py | 59 | ||||
-rw-r--r-- | python/vyos/ifconfig/l2tpv3.py | 12 | ||||
-rw-r--r-- | python/vyos/ifconfig/vxlan.py | 20 | ||||
-rw-r--r-- | python/vyos/ipsec.py | 136 | ||||
-rw-r--r-- | python/vyos/opmode.py | 12 | ||||
-rw-r--r-- | python/vyos/template.py | 4 | ||||
-rw-r--r-- | python/vyos/utils/network.py | 51 | ||||
-rw-r--r-- | python/vyos/utils/process.py | 2 |
13 files changed, 284 insertions, 103 deletions
diff --git a/python/vyos/config.py b/python/vyos/config.py index cca65f0eb..b7ee606a9 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -140,6 +140,7 @@ class Config(object): self._level = [] self._dict_cache = {} + self.dependency_list = [] (self._running_config, self._session_config) = self._config_source.get_configtree_tuple() diff --git a/python/vyos/configdep.py b/python/vyos/configdep.py index 73bd9ea96..e0fe1ddac 100644 --- a/python/vyos/configdep.py +++ b/python/vyos/configdep.py @@ -33,10 +33,9 @@ if typing.TYPE_CHECKING: dependency_dir = os.path.join(directories['data'], 'config-mode-dependencies') -local_dependent_func: dict[str, list[typing.Callable]] = {} +dependency_list: list[typing.Callable] = [] DEBUG = False -FORCE_LOCAL = False def debug_print(s: str): if DEBUG: @@ -50,7 +49,8 @@ def canon_name_of_path(path: str) -> str: return canon_name(script) def caller_name() -> str: - return stack()[2].filename + filename = stack()[2].filename + return canon_name_of_path(filename) def name_of(f: typing.Callable) -> str: return f.__name__ @@ -107,46 +107,47 @@ def run_config_mode_script(script: str, config: 'Config'): mod.generate(c) mod.apply(c) except (VyOSError, ConfigError) as e: - raise ConfigError(repr(e)) + raise ConfigError(str(e)) from e def def_closure(target: str, config: 'Config', tagnode: typing.Optional[str] = None) -> typing.Callable: script = target + '.py' def func_impl(): - if tagnode: + if tagnode is not None: os.environ['VYOS_TAGNODE_VALUE'] = tagnode run_config_mode_script(script, config) + tag_ext = f'_{tagnode}' if tagnode is not None else '' + func_impl.__name__ = f'{target}{tag_ext}' return func_impl def set_dependents(case: str, config: 'Config', tagnode: typing.Optional[str] = None): + global dependency_list + + dependency_list = config.dependency_list + d = get_dependency_dict(config) - k = canon_name_of_path(caller_name()) - tag_ext = f'_{tagnode}' if tagnode is not None else '' - if hasattr(config, 'dependent_func') and not FORCE_LOCAL: - dependent_func = getattr(config, 'dependent_func') - l = dependent_func.setdefault('vyos_configd', []) - else: - dependent_func = local_dependent_func - l = dependent_func.setdefault(k, []) + k = caller_name() + l = dependency_list + for target in d[k][case]: func = def_closure(target, config, tagnode) - func.__name__ = f'{target}{tag_ext}' append_uniq(l, func) - debug_print(f'set_dependents: caller {k}, dependents {names_of(l)}') -def call_dependents(dependent_func: dict = None): - k = canon_name_of_path(caller_name()) - if dependent_func is None or FORCE_LOCAL: - dependent_func = local_dependent_func - l = dependent_func.get(k, []) - else: - l = dependent_func.get('vyos_configd', []) - debug_print(f'call_dependents: caller {k}, dependents {names_of(l)}') + debug_print(f'set_dependents: caller {k}, current dependents {names_of(l)}') + +def call_dependents(): + k = caller_name() + l = dependency_list + debug_print(f'call_dependents: caller {k}, remaining dependents {names_of(l)}') while l: f = l.pop(0) debug_print(f'calling: {f.__name__}') - f() + try: + f() + except ConfigError as e: + s = f'dependent {f.__name__}: {str(e)}' + raise ConfigError(s) from e def called_as_dependent() -> bool: st = stack()[1:] diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py index 5775070e2..bd77ab899 100644 --- a/python/vyos/configtree.py +++ b/python/vyos/configtree.py @@ -1,5 +1,5 @@ # configtree -- a standalone VyOS config file manipulation library (Python bindings) -# Copyright (C) 2018-2022 VyOS maintainers and contributors +# Copyright (C) 2018-2024 VyOS maintainers and contributors # # This library is free software; you can redistribute it and/or modify it under the terms of # the GNU Lesser General Public License as published by the Free Software Foundation; @@ -290,7 +290,7 @@ class ConfigTree(object): else: return True - def list_nodes(self, path): + def list_nodes(self, path, path_must_exist=True): check_path(path) path_str = " ".join(map(str, path)).encode() @@ -298,7 +298,10 @@ class ConfigTree(object): res = json.loads(res_json) if res is None: - raise ConfigTreeError("Path [{}] doesn't exist".format(path_str)) + if path_must_exist: + raise ConfigTreeError("Path [{}] doesn't exist".format(path_str)) + else: + return [] else: return res diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index 9ccd925ce..25ee45391 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -50,3 +50,13 @@ commit_lock = os.path.join(directories['vyos_configdir'], '.lock') component_version_json = os.path.join(directories['data'], 'component-versions.json') config_default = os.path.join(directories['data'], 'config.boot.default') + +rt_symbolic_names = { + # Standard routing tables for Linux & reserved IDs for VyOS + 'default': 253, # Confusingly, a final fallthru, not the default. + 'main': 254, # The actual global table used by iproute2 unless told otherwise. + 'local': 255, # Special kernel loopback table. +} + +rt_global_vrf = rt_symbolic_names['main'] +rt_global_table = rt_symbolic_names['main'] diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index 664df28cc..facd498ca 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -30,6 +30,9 @@ from vyos.utils.dict import dict_search_args from vyos.utils.dict import dict_search_recursive from vyos.utils.process import cmd from vyos.utils.process import run +from vyos.utils.network import get_vrf_tableid +from vyos.defaults import rt_global_table +from vyos.defaults import rt_global_vrf # Conntrack def conntrack_required(conf): @@ -366,10 +369,14 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name): output.append(f'ip{def_suffix} dscp != {{{negated_dscp_str}}}') if 'ipsec' in rule_conf: - if 'match_ipsec' in rule_conf['ipsec']: + if 'match_ipsec_in' in rule_conf['ipsec']: output.append('meta ipsec == 1') - if 'match_none' in rule_conf['ipsec']: + if 'match_none_in' in rule_conf['ipsec']: output.append('meta ipsec == 0') + if 'match_ipsec_out' in rule_conf['ipsec']: + output.append('rt ipsec exists') + if 'match_none_out' in rule_conf['ipsec']: + output.append('rt ipsec missing') if 'fragment' in rule_conf: # Checking for fragmentation after priority -400 is not possible, @@ -469,11 +476,20 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name): if 'mark' in rule_conf['set']: mark = rule_conf['set']['mark'] output.append(f'meta mark set {mark}') + if 'vrf' in rule_conf['set']: + set_table = True + vrf_name = rule_conf['set']['vrf'] + if vrf_name == 'default': + table = rt_global_vrf + else: + # NOTE: VRF->table ID lookup depends on the VRF iface already existing. + table = get_vrf_tableid(vrf_name) if 'table' in rule_conf['set']: set_table = True table = rule_conf['set']['table'] if table == 'main': - table = '254' + table = rt_global_table + if set_table: mark = 0x7FFFFFFF - int(table) output.append(f'meta mark set {mark}') if 'tcp_mss' in rule_conf['set']: diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 117479ade..72d3d3afe 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -37,6 +37,7 @@ from vyos.utils.network import mac2eui64 from vyos.utils.dict import dict_search from vyos.utils.network import get_interface_config from vyos.utils.network import get_interface_namespace +from vyos.utils.network import get_vrf_tableid from vyos.utils.network import is_netns_interface from vyos.utils.process import is_systemd_service_active from vyos.utils.process import run @@ -382,6 +383,9 @@ class Interface(Control): # can not delete ALL interfaces, see below self.flush_addrs() + # remove interface from conntrack VRF interface map + self._del_interface_from_ct_iface_map() + # --------------------------------------------------------------------- # Any class can define an eternal regex in its definition # interface matching the regex will not be deleted @@ -402,29 +406,20 @@ class Interface(Control): if netns: cmd = f'ip netns exec {netns} {cmd}' return self._cmd(cmd) - def _set_vrf_ct_zone(self, vrf): - """ - Add/Remove rules in nftables to associate traffic in VRF to an - individual conntack zone - """ - # Don't allow for netns yet - if 'netns' in self.config: - return None + def _nft_check_and_run(self, nft_command): + # Check if deleting is possible first to avoid raising errors + _, err = self._popen(f'nft --check {nft_command}') + if not err: + # Remove map element + self._cmd(f'nft {nft_command}') - if vrf: - # Get routing table ID for VRF - vrf_table_id = get_interface_config(vrf).get('linkinfo', {}).get( - 'info_data', {}).get('table') - # Add map element with interface and zone ID - if vrf_table_id: - self._cmd(f'nft add element inet vrf_zones ct_iface_map {{ "{self.ifname}" : {vrf_table_id} }}') - else: - nft_del_element = f'delete element inet vrf_zones ct_iface_map {{ "{self.ifname}" }}' - # Check if deleting is possible first to avoid raising errors - _, err = self._popen(f'nft --check {nft_del_element}') - if not err: - # Remove map element - self._cmd(f'nft {nft_del_element}') + def _del_interface_from_ct_iface_map(self): + nft_command = f'delete element inet vrf_zones ct_iface_map {{ "{self.ifname}" }}' + self._nft_check_and_run(nft_command) + + def _add_interface_to_ct_iface_map(self, vrf_table_id: int): + nft_command = f'add element inet vrf_zones ct_iface_map {{ "{self.ifname}" : {vrf_table_id} }}' + self._nft_check_and_run(nft_command) def get_min_mtu(self): """ @@ -597,12 +592,30 @@ class Interface(Control): >>> Interface('eth0').set_vrf() """ + # Don't allow for netns yet + if 'netns' in self.config: + return False + tmp = self.get_interface('vrf') if tmp == vrf: return False + # Get current VRF table ID + old_vrf_tableid = get_vrf_tableid(self.ifname) self.set_interface('vrf', vrf) - self._set_vrf_ct_zone(vrf) + + if vrf: + # Get routing table ID number for VRF + vrf_table_id = get_vrf_tableid(vrf) + # Add map element with interface and zone ID + if vrf_table_id: + # delete old table ID from nftables if it has changed, e.g. interface moved to a different VRF + if old_vrf_tableid and old_vrf_tableid != int(vrf_table_id): + self._del_interface_from_ct_iface_map() + self._add_interface_to_ct_iface_map(vrf_table_id) + else: + self._del_interface_from_ct_iface_map() + return True def set_arp_cache_tmo(self, tmo): diff --git a/python/vyos/ifconfig/l2tpv3.py b/python/vyos/ifconfig/l2tpv3.py index 85a89ef8b..c1f2803ee 100644 --- a/python/vyos/ifconfig/l2tpv3.py +++ b/python/vyos/ifconfig/l2tpv3.py @@ -90,9 +90,17 @@ class L2TPv3If(Interface): """ if self.exists(self.ifname): - # interface is always A/D down. It needs to be enabled explicitly self.set_admin_state('down') + # remove all assigned IP addresses from interface - this is a bit redundant + # as the kernel will remove all addresses on interface deletion + self.flush_addrs() + + # remove interface from conntrack VRF interface map, here explicitly and do not + # rely on the base class implementation as the interface will + # vanish as soon as the l2tp session is deleted + self._del_interface_from_ct_iface_map() + if {'tunnel_id', 'session_id'} <= set(self.config): cmd = 'ip l2tp del session tunnel_id {tunnel_id}' cmd += ' session_id {session_id}' @@ -101,3 +109,5 @@ class L2TPv3If(Interface): if 'tunnel_id' in self.config: cmd = 'ip l2tp del tunnel tunnel_id {tunnel_id}' self._cmd(cmd.format(**self.config)) + + # No need to call the baseclass as the interface is now already gone diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index 918aea202..1023c58d1 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -134,6 +134,19 @@ class VXLANIf(Interface): Controls whether vlan to tunnel mapping is enabled on the port. By default this flag is off. """ + def range_to_dict(vlan_to_vni): + """ Converts dict of ranges to dict """ + result_dict = {} + for vlan, vlan_conf in vlan_to_vni.items(): + vni = vlan_conf['vni'] + vlan_range, vni_range = vlan.split('-'), vni.split('-') + if len(vlan_range) > 1: + vlan_range = range(int(vlan_range[0]), int(vlan_range[1]) + 1) + vni_range = range(int(vni_range[0]), int(vni_range[1]) + 1) + dict_to_add = {str(k): {'vni': str(v)} for k, v in zip(vlan_range, vni_range)} + result_dict.update(dict_to_add) + return result_dict + if not isinstance(state, bool): raise ValueError('Value out of range') @@ -142,7 +155,7 @@ class VXLANIf(Interface): if dict_search('parameters.vni_filter', self.config) != None: cur_vni_filter = get_vxlan_vni_filter(self.ifname) - for vlan, vlan_config in self.config['vlan_to_vni_removed'].items(): + for vlan, vlan_config in range_to_dict(self.config['vlan_to_vni_removed']).items(): # If VNI filtering is enabled, remove matching VNI filter if cur_vni_filter != None: vni = vlan_config['vni'] @@ -159,10 +172,11 @@ class VXLANIf(Interface): if 'vlan_to_vni' in self.config: # Determine current OS Kernel configured VLANs + vlan_vni_mapping = range_to_dict(self.config['vlan_to_vni']) os_configured_vlan_ids = get_vxlan_vlan_tunnels(self.ifname) - add_vlan = list_diff(list(self.config['vlan_to_vni'].keys()), os_configured_vlan_ids) + add_vlan = list_diff(list(vlan_vni_mapping.keys()), os_configured_vlan_ids) - for vlan, vlan_config in self.config['vlan_to_vni'].items(): + for vlan, vlan_config in vlan_vni_mapping.items(): # VLAN mapping already exists - skip if vlan not in add_vlan: continue diff --git a/python/vyos/ipsec.py b/python/vyos/ipsec.py index 4603aab22..28f77565a 100644 --- a/python/vyos/ipsec.py +++ b/python/vyos/ipsec.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -13,31 +13,38 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see <http://www.gnu.org/licenses/>. -#Package to communicate with Strongswan VICI +# Package to communicate with Strongswan VICI + class ViciInitiateError(Exception): """ - VICI can't initiate a session. + VICI can't initiate a session. """ + pass + + class ViciCommandError(Exception): """ - VICI can't execute a command by any reason. + VICI can't execute a command by any reason. """ + pass + def get_vici_sas(): from vici import Session as vici_session try: session = vici_session() except Exception: - raise ViciInitiateError("IPsec not initialized") + raise ViciInitiateError('IPsec not initialized') try: sas = list(session.list_sas()) return sas except Exception: - raise ViciCommandError(f'Failed to get SAs') + raise ViciCommandError('Failed to get SAs') + def get_vici_connections(): from vici import Session as vici_session @@ -45,18 +52,19 @@ def get_vici_connections(): try: session = vici_session() except Exception: - raise ViciInitiateError("IPsec not initialized") + raise ViciInitiateError('IPsec not initialized') try: connections = list(session.list_conns()) return connections except Exception: - raise ViciCommandError(f'Failed to get connections') + raise ViciCommandError('Failed to get connections') + def get_vici_sas_by_name(ike_name: str, tunnel: str) -> list: """ - Find sas by IKE_SA name and/or CHILD_SA name - and return list of OrdinaryDicts with SASs info - If tunnel is not None return value is list of OrdenaryDicts contained only + Find installed SAs by IKE_SA name and/or CHILD_SA name + and return list with SASs info. + If tunnel is not None return a list contained only CHILD_SAs wich names equal tunnel value. :param ike_name: IKE SA name :type ike_name: str @@ -70,7 +78,7 @@ def get_vici_sas_by_name(ike_name: str, tunnel: str) -> list: try: session = vici_session() except Exception: - raise ViciInitiateError("IPsec not initialized") + raise ViciInitiateError('IPsec not initialized') vici_dict = {} if ike_name: vici_dict['ike'] = ike_name @@ -80,7 +88,31 @@ def get_vici_sas_by_name(ike_name: str, tunnel: str) -> list: sas = list(session.list_sas(vici_dict)) return sas except Exception: - raise ViciCommandError(f'Failed to get SAs') + raise ViciCommandError('Failed to get SAs') + + +def get_vici_connection_by_name(ike_name: str) -> list: + """ + Find loaded SAs by IKE_SA name and return list with SASs info + :param ike_name: IKE SA name + :type ike_name: str + :return: list of Ordinary Dicts with SASs + :rtype: list + """ + from vici import Session as vici_session + + try: + session = vici_session() + except Exception: + raise ViciInitiateError('IPsec is not initialized') + vici_dict = {} + if ike_name: + vici_dict['ike'] = ike_name + try: + sas = list(session.list_conns(vici_dict)) + return sas + except Exception: + raise ViciCommandError('Failed to get SAs') def terminate_vici_ikeid_list(ike_id_list: list) -> None: @@ -94,19 +126,17 @@ def terminate_vici_ikeid_list(ike_id_list: list) -> None: try: session = vici_session() except Exception: - raise ViciInitiateError("IPsec not initialized") + raise ViciInitiateError('IPsec is not initialized') try: for ikeid in ike_id_list: - session_generator = session.terminate( - {'ike-id': ikeid, 'timeout': '-1'}) + session_generator = session.terminate({'ike-id': ikeid, 'timeout': '-1'}) # a dummy `for` loop is required because of requirements # from vici. Without a full iteration on the output, the # command to vici may not be executed completely for _ in session_generator: pass except Exception: - raise ViciCommandError( - f'Failed to terminate SA for IKE ids {ike_id_list}') + raise ViciCommandError(f'Failed to terminate SA for IKE ids {ike_id_list}') def terminate_vici_by_name(ike_name: str, child_name: str) -> None: @@ -123,9 +153,9 @@ def terminate_vici_by_name(ike_name: str, child_name: str) -> None: try: session = vici_session() except Exception: - raise ViciInitiateError("IPsec not initialized") + raise ViciInitiateError('IPsec is not initialized') try: - vici_dict: dict= {} + vici_dict: dict = {} if ike_name: vici_dict['ike'] = ike_name if child_name: @@ -138,16 +168,48 @@ def terminate_vici_by_name(ike_name: str, child_name: str) -> None: pass except Exception: if child_name: - raise ViciCommandError( - f'Failed to terminate SA for IPSEC {child_name}') + raise ViciCommandError(f'Failed to terminate SA for IPSEC {child_name}') else: - raise ViciCommandError( - f'Failed to terminate SA for IKE {ike_name}') + raise ViciCommandError(f'Failed to terminate SA for IKE {ike_name}') + + +def vici_initiate_all_child_sa_by_ike(ike_sa_name: str, child_sa_list: list) -> bool: + """ + Initiate IKE SA with scpecified CHILD_SAs in list + + Args: + ike_sa_name (str): an IKE SA connection name + child_sa_list (list): a list of child SA names + + Returns: + bool: a result of initiation command + """ + from vici import Session as vici_session + + try: + session = vici_session() + except Exception: + raise ViciInitiateError('IPsec is not initialized') + + try: + for child_sa_name in child_sa_list: + session_generator = session.initiate( + {'ike': ike_sa_name, 'child': child_sa_name, 'timeout': '-1'} + ) + # a dummy `for` loop is required because of requirements + # from vici. Without a full iteration on the output, the + # command to vici may not be executed completely + for _ in session_generator: + pass + return True + except Exception: + raise ViciCommandError(f'Failed to initiate SA for IKE {ike_sa_name}') -def vici_initiate(ike_sa_name: str, child_sa_name: str, src_addr: str, - dst_addr: str) -> bool: - """Initiate IKE SA connection with specific peer +def vici_initiate( + ike_sa_name: str, child_sa_name: str, src_addr: str, dst_addr: str +) -> bool: + """Initiate IKE SA with one child_sa connection with specific peer Args: ike_sa_name (str): an IKE SA connection name @@ -163,16 +225,18 @@ def vici_initiate(ike_sa_name: str, child_sa_name: str, src_addr: str, try: session = vici_session() except Exception: - raise ViciInitiateError("IPsec not initialized") + raise ViciInitiateError('IPsec is not initialized') try: - session_generator = session.initiate({ - 'ike': ike_sa_name, - 'child': child_sa_name, - 'timeout': '-1', - 'my-host': src_addr, - 'other-host': dst_addr - }) + session_generator = session.initiate( + { + 'ike': ike_sa_name, + 'child': child_sa_name, + 'timeout': '-1', + 'my-host': src_addr, + 'other-host': dst_addr, + } + ) # a dummy `for` loop is required because of requirements # from vici. Without a full iteration on the output, the # command to vici may not be executed completely @@ -180,4 +244,4 @@ def vici_initiate(ike_sa_name: str, child_sa_name: str, src_addr: str, pass return True except Exception: - raise ViciCommandError(f'Failed to initiate SA for IKE {ike_sa_name}')
\ No newline at end of file + raise ViciCommandError(f'Failed to initiate SA for IKE {ike_sa_name}') diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py index 8dab9a4ca..a6c64adfb 100644 --- a/python/vyos/opmode.py +++ b/python/vyos/opmode.py @@ -31,7 +31,15 @@ class Error(Exception): class UnconfiguredSubsystem(Error): """ Requested operation is valid, but cannot be completed - because corresponding subsystem is not configured and running. + because corresponding subsystem is not configured + and thus is not running. + """ + pass + +class UnconfiguredObject(UnconfiguredSubsystem): + """ Requested operation is valid but cannot be completed + because its parameter refers to an object that does not exist + in the system configuration. """ pass @@ -81,7 +89,7 @@ class InternalError(Error): def _is_op_mode_function_name(name): - if re.match(r"^(show|clear|reset|restart|add|update|delete|generate|set|renew)", name): + if re.match(r"^(show|clear|reset|restart|add|update|delete|generate|set|renew|release)", name): return True else: return False diff --git a/python/vyos/template.py b/python/vyos/template.py index e8d7ba669..3507e0940 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -556,8 +556,8 @@ def get_openvpn_cipher(cipher): return openvpn_translate[cipher].upper() return cipher.upper() -@register_filter('openvpn_ncp_ciphers') -def get_openvpn_ncp_ciphers(ciphers): +@register_filter('openvpn_data_ciphers') +def get_openvpn_data_ciphers(ciphers): out = [] for cipher in ciphers: if cipher in openvpn_translate: diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py index 829124b57..8fce08de0 100644 --- a/python/vyos/utils/network.py +++ b/python/vyos/utils/network.py @@ -83,6 +83,19 @@ def get_interface_vrf(interface): return tmp['master'] return 'default' +def get_vrf_tableid(interface: str): + """ Return VRF table ID for given interface name or None """ + from vyos.utils.dict import dict_search + table = None + tmp = get_interface_config(interface) + # Check if we are "the" VRF interface + if dict_search('linkinfo.info_kind', tmp) == 'vrf': + table = tmp['linkinfo']['info_data']['table'] + # or an interface bound to a VRF + elif dict_search('linkinfo.info_slave_kind', tmp) == 'vrf': + table = tmp['linkinfo']['info_slave_data']['table'] + return table + def get_interface_config(interface): """ Returns the used encapsulation protocol for given interface. If interface does not exist, None is returned. @@ -537,22 +550,50 @@ def ipv6_prefix_length(low, high): return None xor = bytearray(a ^ b for a, b in zip(lo, hi)) - + plen = 0 while plen < 128 and xor[plen // 8] == 0: plen += 8 - + if plen == 128: return plen - + for i in range((plen // 8) + 1, 16): if xor[i] != 0: return None - + for i in range(8): msk = ~xor[plen // 8] & 0xff - + if msk == bytemasks[i]: return plen + i + 1 return None + +def get_nft_vrf_zone_mapping() -> dict: + """ + Retrieve current nftables conntrack mapping list from Kernel + + returns: [{'interface': 'red', 'vrf_tableid': 1000}, + {'interface': 'eth2', 'vrf_tableid': 1000}, + {'interface': 'blue', 'vrf_tableid': 2000}] + """ + from json import loads + from jmespath import search + from vyos.utils.process import cmd + output = [] + tmp = loads(cmd('sudo nft -j list table inet vrf_zones')) + # {'nftables': [{'metainfo': {'json_schema_version': 1, + # 'release_name': 'Old Doc Yak #3', + # 'version': '1.0.9'}}, + # {'table': {'family': 'inet', 'handle': 6, 'name': 'vrf_zones'}}, + # {'map': {'elem': [['eth0', 666], + # ['dum0', 666], + # ['wg500', 666], + # ['bond10.666', 666]], + vrf_list = search('nftables[].map.elem | [0]', tmp) + if not vrf_list: + return output + for (vrf_name, vrf_id) in vrf_list: + output.append({'interface' : vrf_name, 'vrf_tableid' : vrf_id}) + return output diff --git a/python/vyos/utils/process.py b/python/vyos/utils/process.py index 60ef87a51..ce880f4a4 100644 --- a/python/vyos/utils/process.py +++ b/python/vyos/utils/process.py @@ -225,7 +225,7 @@ def process_named_running(name: str, cmdline: str=None, timeout: int=0): if not tmp: if time.time() > time_expire: break - time.sleep(0.100) # wait 250ms + time.sleep(0.100) # wait 100ms continue return tmp else: |