summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/vyos/accel_ppp_util.py4
-rw-r--r--python/vyos/firewall.py24
-rw-r--r--python/vyos/ifconfig/ethernet.py2
-rw-r--r--python/vyos/opmode.py5
-rw-r--r--python/vyos/qos/trafficshaper.py101
-rw-r--r--python/vyos/remote.py2
-rw-r--r--python/vyos/system/compat.py15
-rw-r--r--python/vyos/system/grub.py61
-rw-r--r--python/vyos/system/grub_util.py30
-rw-r--r--python/vyos/system/image.py13
10 files changed, 233 insertions, 24 deletions
diff --git a/python/vyos/accel_ppp_util.py b/python/vyos/accel_ppp_util.py
index 2f029e042..d60402e48 100644
--- a/python/vyos/accel_ppp_util.py
+++ b/python/vyos/accel_ppp_util.py
@@ -187,13 +187,13 @@ def verify_accel_ppp_ip_pool(vpn_config):
for ipv6_pool, ipv6_pool_config in vpn_config['client_ipv6_pool'].items():
if 'delegate' in ipv6_pool_config and 'prefix' not in ipv6_pool_config:
raise ConfigError(
- f'IPoE IPv6 deletate-prefix requires IPv6 prefix to be configured in "{ipv6_pool}"!')
+ f'IPv6 delegate-prefix requires IPv6 prefix to be configured in "{ipv6_pool}"!')
if dict_search('authentication.mode', vpn_config) in ['local', 'noauth']:
if not dict_search('client_ip_pool', vpn_config) and not dict_search(
'client_ipv6_pool', vpn_config):
raise ConfigError(
- "L2TP local auth mode requires local client-ip-pool or client-ipv6-pool to be configured!")
+ "Local auth mode requires local client-ip-pool or client-ipv6-pool to be configured!")
if dict_search('client_ip_pool', vpn_config) and not dict_search(
'default_pool', vpn_config):
Warning("'default-pool' is not defined")
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index a2622fa00..eee11bd2d 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -226,6 +226,14 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
operator = '!=' if exclude else '=='
operator = f'& {address_mask} {operator}'
output.append(f'{ip_name} {prefix}addr {operator} @A{def_suffix}_{group_name}')
+ elif 'dynamic_address_group' in group:
+ group_name = group['dynamic_address_group']
+ operator = ''
+ exclude = group_name[0] == "!"
+ if exclude:
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'{ip_name} {prefix}addr {operator} @DA{def_suffix}_{group_name}')
# Generate firewall group domain-group
elif 'domain_group' in group:
group_name = group['domain_group']
@@ -280,7 +288,7 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
operator = '!='
iiface = iiface[1:]
output.append(f'iifname {operator} {{{iiface}}}')
- else:
+ elif 'group' in rule_conf['inbound_interface']:
iiface = rule_conf['inbound_interface']['group']
if iiface[0] == '!':
operator = '!='
@@ -295,7 +303,7 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
operator = '!='
oiface = oiface[1:]
output.append(f'oifname {operator} {{{oiface}}}')
- else:
+ elif 'group' in rule_conf['outbound_interface']:
oiface = rule_conf['outbound_interface']['group']
if oiface[0] == '!':
operator = '!='
@@ -419,6 +427,18 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
output.append('counter')
+ if 'add_address_to_group' in rule_conf:
+ for side in ['destination_address', 'source_address']:
+ if side in rule_conf['add_address_to_group']:
+ prefix = side[0]
+ side_conf = rule_conf['add_address_to_group'][side]
+ dyn_group = side_conf['address_group']
+ if 'timeout' in side_conf:
+ timeout_value = side_conf['timeout']
+ output.append(f'set update ip{def_suffix} {prefix}addr timeout {timeout_value} @DA{def_suffix}_{dyn_group}')
+ else:
+ output.append(f'set update ip{def_suffix} saddr @DA{def_suffix}_{dyn_group}')
+
if 'set' in rule_conf:
output.append(parse_policy_set(rule_conf['set'], def_suffix))
diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py
index dde87149d..c3f5bbf47 100644
--- a/python/vyos/ifconfig/ethernet.py
+++ b/python/vyos/ifconfig/ethernet.py
@@ -452,7 +452,7 @@ class EthernetIf(Interface):
self.set_gso(dict_search('offload.gso', config) != None)
# GSO (generic segmentation offload)
- self.set_hw_tc_offload(dict_search('offload.hw-tc-offload', config) != None)
+ self.set_hw_tc_offload(dict_search('offload.hw_tc_offload', config) != None)
# LRO (large receive offload)
self.set_lro(dict_search('offload.lro', config) != None)
diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py
index 230a85541..e1af1a682 100644
--- a/python/vyos/opmode.py
+++ b/python/vyos/opmode.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2022-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
@@ -81,7 +81,7 @@ class InternalError(Error):
def _is_op_mode_function_name(name):
- if re.match(r"^(show|clear|reset|restart|add|delete|generate|set)", name):
+ if re.match(r"^(show|clear|reset|restart|add|update|delete|generate|set)", name):
return True
else:
return False
@@ -275,4 +275,3 @@ def run(module):
# Other functions should not return anything,
# although they may print their own warnings or status messages
func(**args)
-
diff --git a/python/vyos/qos/trafficshaper.py b/python/vyos/qos/trafficshaper.py
index 0d5f9a8a1..d6705cc77 100644
--- a/python/vyos/qos/trafficshaper.py
+++ b/python/vyos/qos/trafficshaper.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2022-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
@@ -99,7 +99,11 @@ class TrafficShaper(QoSBase):
self._cmd(tmp)
if 'default' in config:
- rate = self._rate_convert(config['default']['bandwidth'])
+ if config['default']['bandwidth'].endswith('%'):
+ percent = config['default']['bandwidth'].rstrip('%')
+ rate = self._rate_convert(config['bandwidth']) * int(percent) // 100
+ else:
+ rate = self._rate_convert(config['default']['bandwidth'])
burst = config['default']['burst']
quantum = config['default']['codel_quantum']
tmp = f'tc class replace dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{default_minor_id:x} htb rate {rate} burst {burst} quantum {quantum}'
@@ -107,7 +111,11 @@ class TrafficShaper(QoSBase):
priority = config['default']['priority']
tmp += f' prio {priority}'
if 'ceiling' in config['default']:
- f_ceil = self._rate_convert(config['default']['ceiling'])
+ if config['default']['ceiling'].endswith('%'):
+ percent = config['default']['ceiling'].rstrip('%')
+ f_ceil = self._rate_convert(config['bandwidth']) * int(percent) // 100
+ else:
+ f_ceil = self._rate_convert(config['default']['ceiling'])
tmp += f' ceil {f_ceil}'
self._cmd(tmp)
@@ -117,8 +125,91 @@ class TrafficShaper(QoSBase):
# call base class
super().update(config, direction)
-class TrafficShaperHFSC(TrafficShaper):
+class TrafficShaperHFSC(QoSBase):
+ _parent = 1
+ qostype = 'shaper_hfsc'
+
+ # https://man7.org/linux/man-pages/man8/tc-hfsc.8.html
def update(self, config, direction):
+ class_id_max = 0
+ if 'class' in config:
+ tmp = list(config['class'])
+ tmp.sort()
+ class_id_max = tmp[-1]
+
+ r2q = 10
+ # bandwidth is a mandatory CLI node
+ speed = self._rate_convert(config['bandwidth'])
+ speed_bps = int(speed) // 8
+
+ # need a bigger r2q if going fast than 16 mbits/sec
+ if (speed_bps // r2q) >= MAXQUANTUM: # integer division
+ r2q = ceil(speed_bps // MAXQUANTUM)
+ else:
+ # if there is a slow class then may need smaller value
+ if 'class' in config:
+ min_speed = speed_bps
+ for cls, cls_options in config['class'].items():
+ # find class with the lowest bandwidth used
+ if 'bandwidth' in cls_options:
+ bw_bps = int(self._rate_convert(cls_options['bandwidth'])) // 8 # bandwidth in bytes per second
+ if bw_bps < min_speed:
+ min_speed = bw_bps
+
+ while (r2q > 1) and (min_speed // r2q) < MINQUANTUM:
+ tmp = r2q -1
+ if (speed_bps // tmp) >= MAXQUANTUM:
+ break
+ r2q = tmp
+
+ default_minor_id = int(class_id_max) +1
+ tmp = f'tc qdisc replace dev {self._interface} root handle {self._parent:x}: hfsc default {default_minor_id:x}' # default is in hex
+ self._cmd(tmp)
+
+ tmp = f'tc class replace dev {self._interface} parent {self._parent:x}: classid {self._parent:x}:1 hfsc sc rate {speed} ul rate {speed}'
+ self._cmd(tmp)
+
+ if 'class' in config:
+ for cls, cls_config in config['class'].items():
+ # class id is used later on and passed as hex, thus this needs to be an int
+ cls = int(cls)
+ # ls m1
+ if cls_config.get('linkshare', {}).get('m1').endswith('%'):
+ percent = cls_config['linkshare']['m1'].rstrip('%')
+ m_one_rate = self._rate_convert(config['bandwidth']) * int(percent) // 100
+ else:
+ m_one_rate = cls_config['linkshare']['m1']
+ # ls m2
+ if cls_config.get('linkshare', {}).get('m2').endswith('%'):
+ percent = cls_config['linkshare']['m2'].rstrip('%')
+ m_two_rate = self._rate_convert(config['bandwidth']) * int(percent) // 100
+ else:
+ m_two_rate = self._rate_convert(cls_config['linkshare']['m2'])
+
+ tmp = f'tc class replace dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{cls:x} hfsc ls m1 {m_one_rate} m2 {m_two_rate} '
+ self._cmd(tmp)
+
+ tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{cls:x} sfq perturb 10'
+ self._cmd(tmp)
+
+ if 'default' in config:
+ # ls m1
+ if config.get('default', {}).get('linkshare', {}).get('m1').endswith('%'):
+ percent = config['default']['linkshare']['m1'].rstrip('%')
+ m_one_rate = self._rate_convert(config['default']['linkshare']['m1']) * int(percent) // 100
+ else:
+ m_one_rate = config['default']['linkshare']['m1']
+ # ls m2
+ if config.get('default', {}).get('linkshare', {}).get('m2').endswith('%'):
+ percent = config['default']['linkshare']['m2'].rstrip('%')
+ m_two_rate = self._rate_convert(config['default']['linkshare']['m2']) * int(percent) // 100
+ else:
+ m_two_rate = self._rate_convert(config['default']['linkshare']['m2'])
+ tmp = f'tc class replace dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{default_minor_id:x} hfsc ls m1 {m_one_rate} m2 {m_two_rate} '
+ self._cmd(tmp)
+
+ tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{default_minor_id:x} sfq perturb 10'
+ self._cmd(tmp)
+
# call base class
super().update(config, direction)
-
diff --git a/python/vyos/remote.py b/python/vyos/remote.py
index b1efcd10b..830770d11 100644
--- a/python/vyos/remote.py
+++ b/python/vyos/remote.py
@@ -148,7 +148,7 @@ class FtpC:
# Almost all FTP servers support the `SIZE' command.
size = conn.size(self.path)
if self.check_space:
- check_storage(path, size)
+ check_storage(location, size)
# No progressbar if we can't determine the size or if the file is too small.
if self.progressbar and size and size > CHUNK_SIZE:
with Progressbar(CHUNK_SIZE / size) as p:
diff --git a/python/vyos/system/compat.py b/python/vyos/system/compat.py
index 436da14e8..37b834ad6 100644
--- a/python/vyos/system/compat.py
+++ b/python/vyos/system/compat.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2023-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
@@ -170,9 +170,12 @@ def prune_vyos_versions(root_dir: str = '') -> None:
if not root_dir:
root_dir = disk.find_persistence()
- for version in grub.version_list():
+ version_files = Path(f'{root_dir}/{grub.GRUB_DIR_VYOS_VERS}').glob('*.cfg')
+
+ for file in version_files:
+ version = Path(file).stem
if not Path(f'{root_dir}/boot/{version}').is_dir():
- grub.version_del(version)
+ grub.version_del(version, root_dir)
def update_cfg_ver(root_dir:str = '') -> int:
@@ -246,13 +249,17 @@ def update_version_list(root_dir: str = '') -> list[dict]:
menu_entries = list(filter(lambda x: x.get('version') != ver,
menu_entries))
+ # reset boot_opts in case of config update
+ for entry in menu_entries:
+ entry['boot_opts'] = grub.get_boot_opts(entry['version'])
+
add = list(set(current_versions) - set(menu_versions))
for ver in add:
last = menu_entries[0].get('version')
new = deepcopy(list(filter(lambda x: x.get('version') == last,
menu_entries)))
for e in new:
- boot_opts = e.get('boot_opts').replace(last, ver)
+ boot_opts = grub.get_boot_opts(ver)
e.update({'version': ver, 'boot_opts': boot_opts})
menu_entries = new + menu_entries
diff --git a/python/vyos/system/grub.py b/python/vyos/system/grub.py
index 781962dd0..2e8b20972 100644
--- a/python/vyos/system/grub.py
+++ b/python/vyos/system/grub.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2023-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
@@ -45,10 +45,14 @@ TMPL_GRUB_MODULES: str = 'grub/grub_modules.j2'
TMPL_GRUB_OPTS: str = 'grub/grub_options.j2'
TMPL_GRUB_COMMON: str = 'grub/grub_common.j2'
+# default boot options
+BOOT_OPTS_STEM: str = 'boot=live rootdelay=5 noautologin net.ifnames=0 biosdevname=0 vyos-union=/boot/'
+
# prepare regexes
REGEX_GRUB_VARS: str = r'^set (?P<variable_name>.+)=[\'"]?(?P<variable_value>.*)(?<![\'"])[\'"]?$'
REGEX_GRUB_MODULES: str = r'^insmod (?P<module_name>.+)$'
REGEX_KERNEL_CMDLINE: str = r'^BOOT_IMAGE=/(?P<boot_type>boot|live)/((?P<image_version>.+)/)?vmlinuz.*$'
+REGEX_GRUB_BOOT_OPTS: str = r'^\s*set boot_opts="(?P<boot_opts>[^$]+)"$'
def install(drive_path: str, boot_dir: str, efi_dir: str, id: str = 'VyOS') -> None:
@@ -95,7 +99,8 @@ def gen_version_uuid(version_name: str) -> str:
def version_add(version_name: str,
root_dir: str = '',
- boot_opts: str = '') -> None:
+ boot_opts: str = '',
+ boot_opts_config = None) -> None:
"""Add a new VyOS version to GRUB loader configuration
Args:
@@ -112,7 +117,9 @@ def version_add(version_name: str,
version_config, TMPL_VYOS_VERSION, {
'version_name': version_name,
'version_uuid': gen_version_uuid(version_name),
- 'boot_opts': boot_opts
+ 'boot_opts_default': BOOT_OPTS_STEM + version_name,
+ 'boot_opts': boot_opts,
+ 'boot_opts_config': boot_opts_config
})
@@ -294,12 +301,43 @@ def vars_write(grub_cfg: str, grub_vars: dict[str, str]) -> None:
"""
render(grub_cfg, TMPL_GRUB_VARS, {'vars': grub_vars})
+def get_boot_opts(version_name: str, root_dir: str = '') -> str:
+ """Read boot_opts setting from version file; return default setting on
+ any failure.
+
+ Args:
+ version_name (str): version name
+ root_dir (str, optional): an optional path to the root directory.
+ Defaults to empty.
+ """
+ if not root_dir:
+ root_dir = disk.find_persistence()
+
+ boot_opts_default: str = BOOT_OPTS_STEM + version_name
+ boot_opts: str = ''
+ regex_filter = re_compile(REGEX_GRUB_BOOT_OPTS)
+ version_config: str = f'{root_dir}/{GRUB_DIR_VYOS_VERS}/{version_name}.cfg'
+ try:
+ config_text: list[str] = Path(version_config).read_text().splitlines()
+ except FileNotFoundError:
+ return boot_opts_default
+ for line in config_text:
+ search_result = regex_filter.fullmatch(line)
+ if search_result:
+ search_dict = search_result.groupdict()
+ boot_opts = search_dict.get('boot_opts', '')
+ break
+
+ if not boot_opts:
+ boot_opts = boot_opts_default
+
+ return boot_opts
def set_default(version_name: str, root_dir: str = '') -> None:
"""Set version as default boot entry
Args:
- version_name (str): versio name
+ version_name (str): version name
root_dir (str, optional): an optional path to the root directory.
Defaults to empty.
"""
@@ -369,3 +407,18 @@ def set_console_speed(console_speed: str, root_dir: str = '') -> None:
vars_current: dict[str, str] = vars_read(vars_file)
vars_current['console_speed'] = str(console_speed)
vars_write(vars_file, vars_current)
+
+def set_kernel_cmdline_options(cmdline_options: str, version_name: str,
+ root_dir: str = '') -> None:
+ """Write additional cmdline options to GRUB configuration
+
+ Args:
+ cmdline_options (str): cmdline options to add to default boot line
+ version_name (str): image version name
+ root_dir (str, optional): an optional path to the root directory.
+ """
+ if not root_dir:
+ root_dir = disk.find_persistence()
+
+ version_add(version_name=version_name, root_dir=root_dir,
+ boot_opts_config=cmdline_options)
diff --git a/python/vyos/system/grub_util.py b/python/vyos/system/grub_util.py
index 9e79d41d4..4a3d8795e 100644
--- a/python/vyos/system/grub_util.py
+++ b/python/vyos/system/grub_util.py
@@ -13,7 +13,7 @@
# 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/>.
-from vyos.system import disk, grub, compat
+from vyos.system import disk, grub, image, compat
@compat.grub_cfg_update
def set_console_speed(console_speed: str, root_dir: str = '') -> None:
@@ -29,6 +29,7 @@ def set_console_speed(console_speed: str, root_dir: str = '') -> None:
grub.set_console_speed(console_speed, root_dir)
+@image.if_not_live_boot
def update_console_speed(console_speed: str, root_dir: str = '') -> None:
"""Update console_speed if different from current value"""
@@ -40,3 +41,30 @@ def update_console_speed(console_speed: str, root_dir: str = '') -> None:
console_speed_current = vars_current.get('console_speed', None)
if console_speed != console_speed_current:
set_console_speed(console_speed, root_dir)
+
+@compat.grub_cfg_update
+def set_kernel_cmdline_options(cmdline_options: str, version: str = '',
+ root_dir: str = '') -> None:
+ """Write Kernel CLI cmdline options to GRUB configuration"""
+ if not root_dir:
+ root_dir = disk.find_persistence()
+
+ if not version:
+ version = image.get_running_image()
+
+ grub.set_kernel_cmdline_options(cmdline_options, version, root_dir)
+
+@image.if_not_live_boot
+def update_kernel_cmdline_options(cmdline_options: str,
+ root_dir: str = '') -> None:
+ """Update Kernel custom cmdline options"""
+ if not root_dir:
+ root_dir = disk.find_persistence()
+
+ version = image.get_running_image()
+
+ boot_opts_current = grub.get_boot_opts(version, root_dir)
+ boot_opts_proposed = grub.BOOT_OPTS_STEM + f'{version} {cmdline_options}'
+
+ if boot_opts_proposed != boot_opts_current:
+ set_kernel_cmdline_options(cmdline_options, version, root_dir)
diff --git a/python/vyos/system/image.py b/python/vyos/system/image.py
index 514275654..5460e6a36 100644
--- a/python/vyos/system/image.py
+++ b/python/vyos/system/image.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2023-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
@@ -15,6 +15,7 @@
from pathlib import Path
from re import compile as re_compile
+from functools import wraps
from tempfile import TemporaryDirectory
from typing import TypedDict
@@ -262,6 +263,16 @@ def is_live_boot() -> bool:
return True
return False
+def if_not_live_boot(func):
+ """Decorator to call function only if not live boot"""
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ if not is_live_boot():
+ ret = func(*args, **kwargs)
+ return ret
+ return None
+ return wrapper
+
def is_running_as_container() -> bool:
if Path('/.dockerenv').exists():
return True