summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/vyos/config_mgmt.py2
-rw-r--r--python/vyos/firewall.py49
-rw-r--r--python/vyos/ifconfig/bond.py38
-rw-r--r--python/vyos/nat.py6
-rw-r--r--python/vyos/qos/base.py16
-rw-r--r--python/vyos/qos/randomdetect.py34
-rw-r--r--python/vyos/system/compat.py10
-rw-r--r--python/vyos/system/image.py10
-rw-r--r--python/vyos/template.py21
-rw-r--r--python/vyos/tpm.py2
-rw-r--r--python/vyos/utils/assertion.py4
-rw-r--r--python/vyos/utils/io.py2
-rw-r--r--python/vyos/version.py12
-rw-r--r--python/vyos/xml_ref/__init__.py6
-rw-r--r--python/vyos/xml_ref/definition.py27
15 files changed, 151 insertions, 88 deletions
diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py
index fc51d781c..70b6ea203 100644
--- a/python/vyos/config_mgmt.py
+++ b/python/vyos/config_mgmt.py
@@ -283,6 +283,8 @@ Proceed ?'''
rollback_ct = self._get_config_tree_revision(rev)
try:
load(rollback_ct, switch='explicit')
+ print('Rollback diff has been applied.')
+ print('Use "compare" to review the changes or "commit" to apply them.')
except LoadConfigError as e:
raise ConfigMgmtError(e) from e
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index d9d605a9d..d7b7b80a8 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -32,7 +32,6 @@ from vyos.utils.process import cmd
from vyos.utils.process import run
# Conntrack
-
def conntrack_required(conf):
required_nodes = ['nat', 'nat66', 'load-balancing wan']
@@ -454,8 +453,28 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
else:
output.append(f'set update ip{def_suffix} saddr @DA{def_suffix}_{dyn_group}')
+ set_table = False
if 'set' in rule_conf:
- output.append(parse_policy_set(rule_conf['set'], def_suffix))
+ # Parse set command used in policy route:
+ if 'connection_mark' in rule_conf['set']:
+ conn_mark = rule_conf['set']['connection_mark']
+ output.append(f'ct mark set {conn_mark}')
+ if 'dscp' in rule_conf['set']:
+ dscp = rule_conf['set']['dscp']
+ output.append(f'ip{def_suffix} dscp set {dscp}')
+ if 'mark' in rule_conf['set']:
+ mark = rule_conf['set']['mark']
+ output.append(f'meta mark set {mark}')
+ if 'table' in rule_conf['set']:
+ set_table = True
+ table = rule_conf['set']['table']
+ if table == 'main':
+ table = '254'
+ mark = 0x7FFFFFFF - int(table)
+ output.append(f'meta mark set {mark}')
+ if 'tcp_mss' in rule_conf['set']:
+ mss = rule_conf['set']['tcp_mss']
+ output.append(f'tcp option maxseg size set {mss}')
if 'action' in rule_conf:
# Change action=return to action=action
@@ -488,6 +507,10 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
if synproxy_ws:
output.append(f'wscale {synproxy_ws} timestamp sack-perm')
+ else:
+ if set_table:
+ output.append('return')
+
output.append(f'comment "{family}-{hook}-{fw_name}-{rule_id}"')
return " ".join(output)
@@ -518,28 +541,6 @@ def parse_time(time):
out.append(f'day {{{",".join(out_days)}}}')
return " ".join(out)
-def parse_policy_set(set_conf, def_suffix):
- out = []
- if 'connection_mark' in set_conf:
- conn_mark = set_conf['connection_mark']
- out.append(f'ct mark set {conn_mark}')
- if 'dscp' in set_conf:
- dscp = set_conf['dscp']
- out.append(f'ip{def_suffix} dscp set {dscp}')
- if 'mark' in set_conf:
- mark = set_conf['mark']
- out.append(f'meta mark set {mark}')
- if 'table' in set_conf:
- table = set_conf['table']
- if table == 'main':
- table = '254'
- mark = 0x7FFFFFFF - int(table)
- out.append(f'meta mark set {mark}')
- if 'tcp_mss' in set_conf:
- mss = set_conf['tcp_mss']
- out.append(f'tcp option maxseg size set {mss}')
- return " ".join(out)
-
# GeoIP
nftables_geoip_conf = '/run/nftables-geoip.conf'
diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py
index c6d0f1cff..b8ea90049 100644
--- a/python/vyos/ifconfig/bond.py
+++ b/python/vyos/ifconfig/bond.py
@@ -18,6 +18,7 @@ import os
from vyos.ifconfig.interface import Interface
from vyos.utils.dict import dict_search
from vyos.utils.assertion import assert_list
+from vyos.utils.assertion import assert_mac
from vyos.utils.assertion import assert_positive
@Interface.register
@@ -54,6 +55,10 @@ class BondIf(Interface):
'validate': lambda v: assert_list(v, ['slow', 'fast']),
'location': '/sys/class/net/{ifname}/bonding/lacp_rate',
},
+ 'bond_system_mac': {
+ 'validate': lambda v: assert_mac(v, test_all_zero=False),
+ 'location': '/sys/class/net/{ifname}/bonding/ad_actor_system',
+ },
'bond_miimon': {
'validate': assert_positive,
'location': '/sys/class/net/{ifname}/bonding/miimon'
@@ -385,6 +390,24 @@ class BondIf(Interface):
"""
return self.set_interface('bond_mode', mode)
+ def set_system_mac(self, mac):
+ """
+ In an AD system, this specifies the mac-address for the actor in
+ protocol packet exchanges (LACPDUs). The value cannot be NULL or
+ multicast. It is preferred to have the local-admin bit set for this
+ mac but driver does not enforce it. If the value is not given then
+ system defaults to using the masters' mac address as actors' system
+ address.
+
+ This parameter has effect only in 802.3ad mode and is available through
+ SysFs interface.
+
+ Example:
+ >>> from vyos.ifconfig import BondIf
+ >>> BondIf('bond0').set_system_mac('00:50:ab:cd:ef:01')
+ """
+ return self.set_interface('bond_system_mac', mac)
+
def update(self, config):
""" General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
@@ -426,14 +449,13 @@ class BondIf(Interface):
Interface(interface).set_admin_state('up')
# Bonding policy/mode - default value, always present
- mode = config.get('mode')
- self.set_mode(mode)
+ self.set_mode(config['mode'])
# LACPDU transmission rate - default value
- if mode == '802.3ad':
+ if config['mode'] == '802.3ad':
self.set_lacp_rate(config.get('lacp_rate'))
- if mode not in ['802.3ad', 'balance-tlb', 'balance-alb']:
+ if config['mode'] not in ['802.3ad', 'balance-tlb', 'balance-alb']:
tmp = dict_search('arp_monitor.interval', config)
value = tmp if (tmp != None) else '0'
self.set_arp_interval(value)
@@ -468,6 +490,14 @@ class BondIf(Interface):
Interface(interface).flush_addrs()
self.add_port(interface)
+ # Add system mac address for 802.3ad - default address is all zero
+ # mode is always present (defaultValue)
+ if config['mode'] == '802.3ad':
+ mac = '00:00:00:00:00:00'
+ if 'system_mac' in config:
+ mac = config['system_mac']
+ self.set_system_mac(mac)
+
# Primary device interface - must be set after 'mode'
value = config.get('primary')
if value: self.set_primary(value)
diff --git a/python/vyos/nat.py b/python/vyos/nat.py
index 2ada29add..e54548788 100644
--- a/python/vyos/nat.py
+++ b/python/vyos/nat.py
@@ -300,12 +300,12 @@ def parse_nat_static_rule(rule_conf, rule_id, nat_type):
output.append('counter')
- if translation_str:
- output.append(translation_str)
-
if 'log' in rule_conf:
output.append(f'log prefix "[{log_prefix}{log_suffix}]"')
+ if translation_str:
+ output.append(translation_str)
+
output.append(f'comment "{log_prefix}"')
return " ".join(output)
diff --git a/python/vyos/qos/base.py b/python/vyos/qos/base.py
index 4173a1a43..98e486e42 100644
--- a/python/vyos/qos/base.py
+++ b/python/vyos/qos/base.py
@@ -90,13 +90,14 @@ class QoSBase:
else:
return value
- def _calc_random_detect_queue_params(self, avg_pkt, max_thr, limit=None, min_thr=None, mark_probability=None):
+ def _calc_random_detect_queue_params(self, avg_pkt, max_thr, limit=None, min_thr=None,
+ mark_probability=None, precedence=0):
params = dict()
avg_pkt = int(avg_pkt)
max_thr = int(max_thr)
mark_probability = int(mark_probability)
limit = int(limit) if limit else 4 * max_thr
- min_thr = int(min_thr) if min_thr else (9 * max_thr) // 18
+ min_thr = int(min_thr) if min_thr else ((9 + precedence) * max_thr) // 18
params['avg_pkt'] = avg_pkt
params['limit'] = limit * avg_pkt
@@ -246,9 +247,15 @@ class QoSBase:
filter_cmd_base += ' protocol all'
if 'match' in cls_config:
- is_filtered = False
+ has_filter = False
for index, (match, match_config) in enumerate(cls_config['match'].items(), start=1):
filter_cmd = filter_cmd_base
+ if not has_filter:
+ for key in ['mark', 'vif', 'ip', 'ipv6']:
+ if key in match_config:
+ has_filter = True
+ break
+
if self.qostype == 'shaper' and 'prio ' not in filter_cmd:
filter_cmd += f' prio {index}'
if 'mark' in match_config:
@@ -331,13 +338,12 @@ class QoSBase:
cls = int(cls)
filter_cmd += f' flowid {self._parent:x}:{cls:x}'
self._cmd(filter_cmd)
- is_filtered = True
vlan_expression = "match.*.vif"
match_vlan = jmespath.search(vlan_expression, cls_config)
if any(tmp in ['exceed', 'bandwidth', 'burst'] for tmp in cls_config) \
- and is_filtered:
+ and has_filter:
# For "vif" "basic match" is used instead of "action police" T5961
if not match_vlan:
filter_cmd += f' action police'
diff --git a/python/vyos/qos/randomdetect.py b/python/vyos/qos/randomdetect.py
index d7d84260f..a3a39da36 100644
--- a/python/vyos/qos/randomdetect.py
+++ b/python/vyos/qos/randomdetect.py
@@ -21,33 +21,25 @@ class RandomDetect(QoSBase):
# https://man7.org/linux/man-pages/man8/tc.8.html
def update(self, config, direction):
- tmp = f'tc qdisc add dev {self._interface} root handle {self._parent}:0 dsmark indices 8 set_tc_index'
+ # # Generalized Random Early Detection
+ handle = self._parent
+ tmp = f'tc qdisc add dev {self._interface} root handle {self._parent}:0 gred setup DPs 8 default 0 grio'
self._cmd(tmp)
-
- tmp = f'tc filter add dev {self._interface} parent {self._parent}:0 protocol ip prio 1 tcindex mask 0xe0 shift 5'
- self._cmd(tmp)
-
- # Generalized Random Early Detection
- handle = self._parent +1
- tmp = f'tc qdisc add dev {self._interface} parent {self._parent}:0 handle {handle}:0 gred setup DPs 8 default 0 grio'
- self._cmd(tmp)
-
bandwidth = self._rate_convert(config['bandwidth'])
# set VQ (virtual queue) parameters
for precedence, precedence_config in config['precedence'].items():
precedence = int(precedence)
- avg_pkt = int(precedence_config['average_packet'])
- limit = int(precedence_config['queue_limit']) * avg_pkt
- min_val = int(precedence_config['minimum_threshold']) * avg_pkt
- max_val = int(precedence_config['maximum_threshold']) * avg_pkt
-
- tmp = f'tc qdisc change dev {self._interface} handle {handle}:0 gred limit {limit} min {min_val} max {max_val} avpkt {avg_pkt} '
-
- burst = (2 * int(precedence_config['minimum_threshold']) + int(precedence_config['maximum_threshold'])) // 3
- probability = 1 / int(precedence_config['mark_probability'])
- tmp += f'burst {burst} bandwidth {bandwidth} probability {probability} DP {precedence} prio {8 - precedence:x}'
-
+ qparams = self._calc_random_detect_queue_params(
+ avg_pkt=precedence_config.get('average_packet'),
+ max_thr=precedence_config.get('maximum_threshold'),
+ limit=precedence_config.get('queue_limit'),
+ min_thr=precedence_config.get('minimum_threshold'),
+ mark_probability=precedence_config.get('mark_probability'),
+ precedence=precedence
+ )
+ tmp = f'tc qdisc change dev {self._interface} handle {handle}:0 gred limit {qparams["limit"]} min {qparams["min_val"]} max {qparams["max_val"]} avpkt {qparams["avg_pkt"]} '
+ tmp += f'burst {qparams["burst"]} bandwidth {bandwidth} probability {qparams["probability"]} DP {precedence} prio {8 - precedence:x}'
self._cmd(tmp)
# call base class
diff --git a/python/vyos/system/compat.py b/python/vyos/system/compat.py
index 1b487c1d2..d35bddea2 100644
--- a/python/vyos/system/compat.py
+++ b/python/vyos/system/compat.py
@@ -220,14 +220,8 @@ def get_default(data: dict, root_dir: str = '') -> Union[int, None]:
sublist = list(filter(lambda x: (x.get('version') == image_name and
x.get('console_type') == console_type and
- x.get('console_num') == console_num and
x.get('bootmode') == 'normal'),
menu_entries))
- # legacy images added with legacy tools omitted 'ttyUSB'; if entry not
- # available, default to initial entry of version
- if not sublist:
- sublist = list(filter(lambda x: x.get('version') == image_name,
- menu_entries))
if sublist:
return menu_entries.index(sublist[0])
@@ -253,6 +247,10 @@ def update_version_list(root_dir: str = '') -> list[dict]:
menu_entries = parse_menuentries(grub_cfg_main)
menu_versions = find_versions(menu_entries)
+ # remove deprecated console-type ttyUSB
+ menu_entries = list(filter(lambda x: x.get('console_type') != 'ttyUSB',
+ menu_entries))
+
# get list of versions added/removed by image-tools
current_versions = grub.version_list(root_dir)
diff --git a/python/vyos/system/image.py b/python/vyos/system/image.py
index ba9a6dfa7..aae52e770 100644
--- a/python/vyos/system/image.py
+++ b/python/vyos/system/image.py
@@ -18,8 +18,9 @@ from re import compile as re_compile
from functools import wraps
from tempfile import TemporaryDirectory
from typing import TypedDict
+from json import loads
-from vyos import version
+from vyos.defaults import directories
from vyos.system import disk, grub
# Define variables
@@ -201,9 +202,12 @@ def get_running_image() -> str:
if running_image_result:
running_image: str = running_image_result.groupdict().get(
'image_version', '')
- # we need to have a fallback for live systems
+ # we need to have a fallback for live systems:
+ # explicit read from version file
if not running_image:
- running_image: str = version.get_version()
+ json_data: str = Path(directories['data']).joinpath('version.json').read_text()
+ dict_data: dict = loads(json_data)
+ running_image: str = dict_data['version']
return running_image
diff --git a/python/vyos/template.py b/python/vyos/template.py
index ac77e8a3d..fbc5f1456 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -25,6 +25,14 @@ from vyos.utils.file import makedir
from vyos.utils.permission import chmod
from vyos.utils.permission import chown
+# We use a mutable global variable for the default template directory
+# to make it possible to call scripts from this repository
+# outside of live VyOS systems.
+# If something (like the image build scripts)
+# want to call a script, they can modify the default location
+# to the repository path.
+DEFAULT_TEMPLATE_DIR = directories["templates"]
+
# Holds template filters registered via register_filter()
_FILTERS = {}
_TESTS = {}
@@ -35,18 +43,7 @@ def _get_environment(location=None):
from os import getenv
if location is None:
- # Sometimes functions that rely on templates need to be executed outside of VyOS installations:
- # for example, installer functions are executed for image builds,
- # and anything may be invoked for testing from a developer's machine.
- # This environment variable allows running any unmodified code
- # with a custom template location.
- location_env_var = getenv("VYOS_TEMPLATE_DIR")
- if location_env_var:
- print(f"Using environment variable {location_env_var}")
- template_dir = location_env_var
- else:
- template_dir = directories["templates"]
- loc_loader=FileSystemLoader(template_dir)
+ loc_loader=FileSystemLoader(DEFAULT_TEMPLATE_DIR)
else:
loc_loader=FileSystemLoader(location)
env = Environment(
diff --git a/python/vyos/tpm.py b/python/vyos/tpm.py
index b9f28546f..a24f149fd 100644
--- a/python/vyos/tpm.py
+++ b/python/vyos/tpm.py
@@ -15,7 +15,7 @@
import os
import tempfile
-from vyos.util import rc_cmd
+from vyos.utils.process import rc_cmd
default_pcrs = ['0','2','4','7']
tpm_handle = 0x81000000
diff --git a/python/vyos/utils/assertion.py b/python/vyos/utils/assertion.py
index 1aaa54dff..c7fa220c3 100644
--- a/python/vyos/utils/assertion.py
+++ b/python/vyos/utils/assertion.py
@@ -53,7 +53,7 @@ def assert_mtu(mtu, ifname):
if (max_mtu and cur_mtu > max_mtu) or cur_mtu > 65536:
raise ValueError(f'MTU is too small for interface "{ifname}": {mtu} > {max_mtu}')
-def assert_mac(m):
+def assert_mac(m, test_all_zero=True):
split = m.split(':')
size = len(split)
@@ -74,7 +74,7 @@ def assert_mac(m):
raise ValueError(f'{m} is a multicast MAC address')
# overall mac address is not allowed to be 00:00:00:00:00:00
- if sum(octets) == 0:
+ if test_all_zero and sum(octets) == 0:
raise ValueError('00:00:00:00:00:00 is not a valid MAC address')
if octets[:5] == (0, 0, 94, 0, 1):
diff --git a/python/vyos/utils/io.py b/python/vyos/utils/io.py
index a8c430f28..205210b66 100644
--- a/python/vyos/utils/io.py
+++ b/python/vyos/utils/io.py
@@ -72,6 +72,8 @@ def ask_yes_no(question, default=False) -> bool:
stdout.write("Please respond with yes/y or no/n\n")
except EOFError:
stdout.write("\nPlease respond with yes/y or no/n\n")
+ except KeyboardInterrupt:
+ return False
def is_interactive():
"""Try to determine if the routine was called from an interactive shell."""
diff --git a/python/vyos/version.py b/python/vyos/version.py
index b5ed2705b..86e96d0ec 100644
--- a/python/vyos/version.py
+++ b/python/vyos/version.py
@@ -33,11 +33,11 @@ import os
import requests
import vyos.defaults
+from vyos.system.image import is_live_boot
from vyos.utils.file import read_file
from vyos.utils.file import read_json
from vyos.utils.process import popen
-from vyos.utils.process import run
from vyos.utils.process import DEVNULL
version_file = os.path.join(vyos.defaults.directories['data'], 'version.json')
@@ -81,16 +81,14 @@ def get_full_version_data(fname=version_file):
else:
version_data['system_type'] = f"{hypervisor} guest"
- # Get boot type, it can be livecd, installed image, or, possible, a system installed
- # via legacy "install system" mechanism
+ # Get boot type, it can be livecd or installed image
# In installed images, the squashfs image file is named after its image version,
# while on livecd it's just "filesystem.squashfs", that's how we tell a livecd boot
# from an installed image
- boot_via = "installed image"
- if run(""" grep -e '^overlay.*/filesystem.squashfs' /proc/mounts >/dev/null""") == 0:
+ if is_live_boot():
boot_via = "livecd"
- elif run(""" grep '^overlay /' /proc/mounts >/dev/null """) != 0:
- boot_via = "legacy non-image installation"
+ else:
+ boot_via = "installed image"
version_data['boot_via'] = boot_via
# Get hardware details from DMI
diff --git a/python/vyos/xml_ref/__init__.py b/python/vyos/xml_ref/__init__.py
index bf434865d..2ba3da4e8 100644
--- a/python/vyos/xml_ref/__init__.py
+++ b/python/vyos/xml_ref/__init__.py
@@ -53,6 +53,12 @@ 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 priority(path: list) -> str:
+ return load_reference().priority(path)
+
def cli_defined(path: list, node: str, non_local=False) -> bool:
return load_reference().cli_defined(path, node, non_local=non_local)
diff --git a/python/vyos/xml_ref/definition.py b/python/vyos/xml_ref/definition.py
index c90c5ddbc..c85835ffd 100644
--- a/python/vyos/xml_ref/definition.py
+++ b/python/vyos/xml_ref/definition.py
@@ -135,6 +135,33 @@ class Xml:
d = self._get_ref_path(path)
return self._is_leaf_node(d)
+ def _least_upper_data(self, path: list, name: str) -> str:
+ ref_path = path.copy()
+ d = self.ref
+ data = ''
+ while ref_path and d:
+ d = d.get(ref_path[0], {})
+ ref_path.pop(0)
+ if self._is_tag_node(d) and ref_path:
+ 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
+
+ return data
+
+ def owner(self, path: list) -> str:
+ from pathlib import Path
+ data = self._least_upper_data(path, 'owner')
+ if data:
+ data = Path(data.split()[0]).name
+ return data
+
+ def priority(self, path: list) -> str:
+ return self._least_upper_data(path, 'priority')
+
@staticmethod
def _dict_get(d: dict, path: list) -> dict:
for i in path: