diff options
Diffstat (limited to 'python')
-rw-r--r-- | python/vyos/configdep.py | 26 | ||||
-rw-r--r-- | python/vyos/configdiff.py | 37 | ||||
-rwxr-xr-x | python/vyos/firewall.py | 27 | ||||
-rw-r--r-- | python/vyos/nat.py | 10 | ||||
-rw-r--r-- | python/vyos/utils/convert.py | 62 | ||||
-rw-r--r-- | python/vyos/utils/dict.py | 1 | ||||
-rw-r--r-- | python/vyos/xml_ref/__init__.py | 6 | ||||
-rw-r--r-- | python/vyos/xml_ref/definition.py | 22 |
8 files changed, 162 insertions, 29 deletions
diff --git a/python/vyos/configdep.py b/python/vyos/configdep.py index e0fe1ddac..cf7c9d543 100644 --- a/python/vyos/configdep.py +++ b/python/vyos/configdep.py @@ -95,7 +95,8 @@ def get_dependency_dict(config: 'Config') -> dict: setattr(config, 'cached_dependency_dict', d) return d -def run_config_mode_script(script: str, config: 'Config'): +def run_config_mode_script(target: str, config: 'Config'): + script = target + '.py' path = os.path.join(directories['conf_mode'], script) name = canon_name(script) mod = load_as_module(name, path) @@ -109,15 +110,34 @@ def run_config_mode_script(script: str, config: 'Config'): except (VyOSError, ConfigError) as e: raise ConfigError(str(e)) from e +def run_conditionally(target: str, tagnode: str, config: 'Config'): + tag_ext = f'_{tagnode}' if tagnode else '' + script_name = f'{target}{tag_ext}' + + scripts_called = getattr(config, 'scripts_called', []) + commit_scripts = getattr(config, 'commit_scripts', []) + + debug_print(f'scripts_called: {scripts_called}') + debug_print(f'commit_scripts: {commit_scripts}') + + if script_name in commit_scripts and script_name not in scripts_called: + debug_print(f'dependency {script_name} deferred to priority') + return + + run_config_mode_script(target, config) + def def_closure(target: str, config: 'Config', tagnode: typing.Optional[str] = None) -> typing.Callable: - script = target + '.py' def func_impl(): + tag_value = '' if tagnode is not None: os.environ['VYOS_TAGNODE_VALUE'] = tagnode - run_config_mode_script(script, config) + tag_value = tagnode + run_conditionally(target, tag_value, 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', diff --git a/python/vyos/configdiff.py b/python/vyos/configdiff.py index f975df45d..b6d4a5558 100644 --- a/python/vyos/configdiff.py +++ b/python/vyos/configdiff.py @@ -15,6 +15,7 @@ from enum import IntFlag from enum import auto +from itertools import chain from vyos.config import Config from vyos.configtree import DiffTree @@ -22,7 +23,10 @@ from vyos.configdict import dict_merge from vyos.utils.dict import get_sub_dict from vyos.utils.dict import mangle_dict_keys from vyos.utils.dict import dict_search_args +from vyos.utils.dict import dict_to_key_paths from vyos.xml_ref import get_defaults +from vyos.xml_ref import owner +from vyos.xml_ref import priority class ConfigDiffError(Exception): """ @@ -94,6 +98,39 @@ def get_config_diff(config, key_mangling=None): return ConfigDiff(config, key_mangling, diff_tree=diff_t, diff_dict=diff_d) +def get_commit_scripts(config) -> list: + """Return the list of config scripts to be executed by commit + + Return a list of the scripts to be called by commit for the proposed + config. The list is ordered by priority for reference, however, the + actual order of execution by the commit algorithm is not reflected + (delete vs. add queue), nor needed for current use. + """ + if not config or not isinstance(config, Config): + raise TypeError("argument must me a Config instance") + + if hasattr(config, 'commit_scripts'): + return getattr(config, 'commit_scripts') + + D = get_config_diff(config) + d = D._diff_dict + s = set() + for p in chain(dict_to_key_paths(d['sub']), dict_to_key_paths(d['add'])): + p_owner = owner(p, with_tag=True) + if not p_owner: + continue + p_priority = priority(p) + if not p_priority: + # default priority in legacy commit-algorithm + p_priority = 0 + p_priority = int(p_priority) + s.add((p_priority, p_owner)) + + res = [x[1] for x in sorted(s, key=lambda x: x[0])] + setattr(config, 'commit_scripts', res) + + return res + class ConfigDiff(object): """ The class of config changes as represented by comparison between the diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index f0cf3c924..64fed8177 100755 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -151,6 +151,20 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name): proto = '{tcp, udp}' output.append(f'meta l4proto {operator} {proto}') + if 'ethernet_type' in rule_conf: + ether_type_mapping = { + '802.1q': '8021q', + '802.1ad': '8021ad', + 'ipv6': 'ip6', + 'ipv4': 'ip', + 'arp': 'arp' + } + ether_type = rule_conf['ethernet_type'] + operator = '!=' if ether_type.startswith('!') else '' + ether_type = ether_type.lstrip('!') + ether_type = ether_type_mapping.get(ether_type, ether_type) + output.append(f'ether type {operator} {ether_type}') + for side in ['destination', 'source']: if side in rule_conf: prefix = side[0] @@ -482,6 +496,19 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name): output.append(f'vlan id {rule_conf["vlan"]["id"]}') if 'priority' in rule_conf['vlan']: output.append(f'vlan pcp {rule_conf["vlan"]["priority"]}') + if 'ethernet_type' in rule_conf['vlan']: + ether_type_mapping = { + '802.1q': '8021q', + '802.1ad': '8021ad', + 'ipv6': 'ip6', + 'ipv4': 'ip', + 'arp': 'arp' + } + ether_type = rule_conf['vlan']['ethernet_type'] + operator = '!=' if ether_type.startswith('!') else '' + ether_type = ether_type.lstrip('!') + ether_type = ether_type_mapping.get(ether_type, ether_type) + output.append(f'vlan type {operator} {ether_type}') if 'log' in rule_conf: action = rule_conf['action'] if 'action' in rule_conf else 'accept' diff --git a/python/vyos/nat.py b/python/vyos/nat.py index e54548788..5fab3c2a1 100644 --- a/python/vyos/nat.py +++ b/python/vyos/nat.py @@ -199,7 +199,10 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False): if group_name[0] == '!': operator = '!=' group_name = group_name[1:] - output.append(f'{ip_prefix} {prefix}addr {operator} @A_{group_name}') + if ipv6: + output.append(f'{ip_prefix} {prefix}addr {operator} @A6_{group_name}') + else: + output.append(f'{ip_prefix} {prefix}addr {operator} @A_{group_name}') # Generate firewall group domain-group elif 'domain_group' in group and not (ignore_type_addr and target == nat_type): group_name = group['domain_group'] @@ -214,7 +217,10 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False): if group_name[0] == '!': operator = '!=' group_name = group_name[1:] - output.append(f'{ip_prefix} {prefix}addr {operator} @N_{group_name}') + if ipv6: + output.append(f'{ip_prefix} {prefix}addr {operator} @N6_{group_name}') + else: + output.append(f'{ip_prefix} {prefix}addr {operator} @N_{group_name}') if 'mac_group' in group: group_name = group['mac_group'] operator = '' diff --git a/python/vyos/utils/convert.py b/python/vyos/utils/convert.py index 41e65081f..dd4266f57 100644 --- a/python/vyos/utils/convert.py +++ b/python/vyos/utils/convert.py @@ -12,41 +12,72 @@ # # 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/>. +import re + +# Define the number of seconds in each time unit +time_units = { + 'y': 60 * 60 * 24 * 365.25, # year + 'w': 60 * 60 * 24 * 7, # week + 'd': 60 * 60 * 24, # day + 'h': 60 * 60, # hour + 'm': 60, # minute + 's': 1 # second +} + + +def human_to_seconds(time_str): + """ Converts a human-readable interval such as 1w4d18h35m59s + to number of seconds + """ + + time_patterns = { + 'y': r'(\d+)\s*y', + 'w': r'(\d+)\s*w', + 'd': r'(\d+)\s*d', + 'h': r'(\d+)\s*h', + 'm': r'(\d+)\s*m', + 's': r'(\d+)\s*s' + } + + total_seconds = 0 + + for unit, pattern in time_patterns.items(): + match = re.search(pattern, time_str) + if match: + value = int(match.group(1)) + total_seconds += value * time_units[unit] + + return int(total_seconds) + def seconds_to_human(s, separator=""): """ Converts number of seconds passed to a human-readable interval such as 1w4d18h35m59s """ s = int(s) - - year = 60 * 60 * 24 * 365.25 - week = 60 * 60 * 24 * 7 - day = 60 * 60 * 24 - hour = 60 * 60 - result = [] - years = s // year + years = s // time_units['y'] if years > 0: result.append(f'{int(years)}y') - s = int(s % year) + s = int(s % time_units['y']) - weeks = s // week + weeks = s // time_units['w'] if weeks > 0: result.append(f'{weeks}w') - s = s % week + s = s % time_units['w'] - days = s // day + days = s // time_units['d'] if days > 0: result.append(f'{days}d') - s = s % day + s = s % time_units['d'] - hours = s // hour + hours = s // time_units['h'] if hours > 0: result.append(f'{hours}h') - s = s % hour + s = s % time_units['h'] - minutes = s // 60 + minutes = s // time_units['m'] if minutes > 0: result.append(f'{minutes}m') s = s % 60 @@ -57,6 +88,7 @@ def seconds_to_human(s, separator=""): return separator.join(result) + def bytes_to_human(bytes, initial_exponent=0, precision=2, int_below_exponent=0): """ Converts a value in bytes to a human-readable size string like 640 KB diff --git a/python/vyos/utils/dict.py b/python/vyos/utils/dict.py index 1eb6abcd5..1a7a6b96f 100644 --- a/python/vyos/utils/dict.py +++ b/python/vyos/utils/dict.py @@ -267,6 +267,7 @@ def dict_to_paths_values(conf: dict) -> dict: dict_of_options[path] = dict_search(path,conf) return dict_of_options + def dict_to_key_paths(d: dict) -> list: """ Generator to return list of key paths from dict of list[str]|str """ diff --git a/python/vyos/xml_ref/__init__.py b/python/vyos/xml_ref/__init__.py index 91ce394f7..99d8432d2 100644 --- a/python/vyos/xml_ref/__init__.py +++ b/python/vyos/xml_ref/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -54,8 +54,8 @@ def is_valueless(path: list) -> bool: def is_leaf(path: list) -> bool: return load_reference().is_leaf(path) -def owner(path: list) -> str: - return load_reference().owner(path) +def owner(path: list, with_tag=False) -> str: + return load_reference().owner(path, with_tag=with_tag) def priority(path: list) -> str: return load_reference().priority(path) diff --git a/python/vyos/xml_ref/definition.py b/python/vyos/xml_ref/definition.py index c85835ffd..5ff28daed 100644 --- a/python/vyos/xml_ref/definition.py +++ b/python/vyos/xml_ref/definition.py @@ -1,4 +1,4 @@ -# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -139,28 +139,38 @@ class Xml: ref_path = path.copy() d = self.ref data = '' + tag = '' while ref_path and d: + tag_val = '' d = d.get(ref_path[0], {}) ref_path.pop(0) if self._is_tag_node(d) and ref_path: + tag_val = ref_path[0] ref_path.pop(0) if self._is_leaf_node(d) and ref_path: ref_path.pop(0) res = self._get_ref_node_data(d, name) if res is not None: data = res + tag = tag_val - return data + return data, tag - def owner(self, path: list) -> str: + def owner(self, path: list, with_tag=False) -> str: from pathlib import Path - data = self._least_upper_data(path, 'owner') + data, tag = self._least_upper_data(path, 'owner') + tag_ext = f'_{tag}' if tag else '' if data: - data = Path(data.split()[0]).name + if with_tag: + data = Path(data.split()[0]).stem + data = f'{data}{tag_ext}' + else: + data = Path(data.split()[0]).name return data def priority(self, path: list) -> str: - return self._least_upper_data(path, 'priority') + data, _ = self._least_upper_data(path, 'priority') + return data @staticmethod def _dict_get(d: dict, path: list) -> dict: |