summaryrefslogtreecommitdiff
path: root/python/vyos
diff options
context:
space:
mode:
Diffstat (limited to 'python/vyos')
-rw-r--r--python/vyos/configdep.py26
-rw-r--r--python/vyos/configdiff.py37
-rwxr-xr-xpython/vyos/firewall.py27
-rw-r--r--python/vyos/nat.py10
-rw-r--r--python/vyos/utils/convert.py62
-rw-r--r--python/vyos/utils/dict.py1
-rw-r--r--python/vyos/xml_ref/__init__.py6
-rw-r--r--python/vyos/xml_ref/definition.py22
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: