diff options
Diffstat (limited to 'python/vyos')
-rw-r--r-- | python/vyos/config_mgmt.py | 57 | ||||
-rw-r--r-- | python/vyos/configdict.py | 99 | ||||
-rw-r--r-- | python/vyos/configsession.py | 19 | ||||
-rw-r--r-- | python/vyos/configtree.py | 13 | ||||
-rw-r--r-- | python/vyos/configverify.py | 2 | ||||
-rw-r--r-- | python/vyos/ethtool.py | 2 | ||||
-rw-r--r-- | python/vyos/ifconfig/interface.py | 39 | ||||
-rw-r--r-- | python/vyos/qos/base.py | 169 | ||||
-rw-r--r-- | python/vyos/qos/trafficshaper.py | 12 | ||||
-rw-r--r-- | python/vyos/util.py | 21 | ||||
-rw-r--r-- | python/vyos/utils/__init__.py | 16 | ||||
-rw-r--r-- | python/vyos/utils/dict.py | 21 | ||||
-rw-r--r-- | python/vyos/utils/network.py | 30 | ||||
-rw-r--r-- | python/vyos/validate.py | 4 | ||||
-rw-r--r-- | python/vyos/xml_ref/__init__.py | 3 | ||||
-rw-r--r-- | python/vyos/xml_ref/definition.py | 25 |
16 files changed, 332 insertions, 200 deletions
diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py index fade3081c..26114149f 100644 --- a/python/vyos/config_mgmt.py +++ b/python/vyos/config_mgmt.py @@ -26,6 +26,7 @@ from tabulate import tabulate from vyos.config import Config from vyos.configtree import ConfigTree, ConfigTreeError, show_diff from vyos.defaults import directories +from vyos.version import get_full_version_data from vyos.util import is_systemd_service_active, ask_yes_no, rc_cmd SAVE_CONFIG = '/opt/vyatta/sbin/vyatta-save-config.pl' @@ -56,6 +57,44 @@ formatter = logging.Formatter('%(funcName)s: %(levelname)s:%(message)s') ch.setFormatter(formatter) logger.addHandler(ch) +def save_config(target): + cmd = f'{SAVE_CONFIG} {target}' + rc, out = rc_cmd(cmd) + if rc != 0: + logger.critical(f'save config failed: {out}') + +def unsaved_commits() -> bool: + if get_full_version_data()['boot_via'] == 'livecd': + return False + tmp_save = '/tmp/config.running' + save_config(tmp_save) + ret = not cmp(tmp_save, config_file, shallow=False) + os.unlink(tmp_save) + return ret + +def get_file_revision(rev: int): + revision = os.path.join(archive_dir, f'config.boot.{rev}.gz') + try: + with gzip.open(revision) as f: + r = f.read().decode() + except FileNotFoundError: + logger.warning(f'commit revision {rev} not available') + return '' + return r + +def get_config_tree_revision(rev: int): + c = get_file_revision(rev) + return ConfigTree(c) + +def is_node_revised(path: list = [], rev1: int = 1, rev2: int = 0) -> bool: + from vyos.configtree import DiffTree + left = get_config_tree_revision(rev1) + right = get_config_tree_revision(rev2) + diff_tree = DiffTree(left, right) + if diff_tree.add.exists(path) or diff_tree.sub.exists(path): + return True + return False + class ConfigMgmtError(Exception): pass @@ -98,20 +137,6 @@ class ConfigMgmt: self.active_config = config._running_config self.working_config = config._session_config - @staticmethod - def save_config(target): - cmd = f'{SAVE_CONFIG} {target}' - rc, out = rc_cmd(cmd) - if rc != 0: - logger.critical(f'save config failed: {out}') - - def _unsaved_commits(self) -> bool: - tmp_save = '/tmp/config.boot.check-save' - self.save_config(tmp_save) - ret = not cmp(tmp_save, config_file, shallow=False) - os.unlink(tmp_save) - return ret - # Console script functions # def commit_confirm(self, minutes: int=DEFAULT_TIME_MINUTES, @@ -123,7 +148,7 @@ class ConfigMgmt: msg = 'Another confirm is pending' return msg, 1 - if self._unsaved_commits(): + if unsaved_commits(): W = '\nYou should save previous commits before commit-confirm !\n' else: W = '' @@ -450,7 +475,7 @@ Proceed ?''' ext = os.getpid() tmp_save = f'/tmp/config.boot.{ext}' - self.save_config(tmp_save) + save_config(tmp_save) try: if cmp(tmp_save, archive_config_file, shallow=False): diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 6ab5c252c..9618ec93e 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -389,7 +389,7 @@ def get_pppoe_interfaces(conf, vrf=None): return pppoe_interfaces -def get_interface_dict(config, base, ifname=''): +def get_interface_dict(config, base, ifname='', recursive_defaults=True): """ Common utility function to retrieve and mangle the interfaces configuration from the CLI input nodes. All interfaces have a common base where value @@ -405,46 +405,23 @@ def get_interface_dict(config, base, ifname=''): raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') ifname = os.environ['VYOS_TAGNODE_VALUE'] - # retrieve interface default values - default_values = defaults(base) - - # We take care about VLAN (vif, vif-s, vif-c) default values later on when - # parsing vlans in default dict and merge the "proper" values in correctly, - # see T2665. - for vif in ['vif', 'vif_s']: - if vif in default_values: del default_values[vif] - - dict = config.get_config_dict(base + [ifname], key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - # Check if interface has been removed. We must use exists() as # get_config_dict() will always return {} - even when an empty interface # node like the following exists. # +macsec macsec1 { # +} if not config.exists(base + [ifname]): + dict = config.get_config_dict(base + [ifname], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) dict.update({'deleted' : {}}) - - # Add interface instance name into dictionary - dict.update({'ifname': ifname}) - - # Check if QoS policy applied on this interface - See ifconfig.interface.set_mirror_redirect() - if config.exists(['qos', 'interface', ifname]): - dict.update({'traffic_policy': {}}) - - # XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely - # remove the default values from the dict. - if 'dhcpv6_options' not in dict: - if 'dhcpv6_options' in default_values: - del default_values['dhcpv6_options'] - - # We have gathered the dict representation of the CLI, but there are - # default options which we need to update into the dictionary retrived. - # But we should only add them when interface is not deleted - as this might - # confuse parsers - if 'deleted' not in dict: - dict = dict_merge(default_values, dict) + else: + # Get config_dict with default values + dict = config.get_config_dict(base + [ifname], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_defaults=True, + with_recursive_defaults=recursive_defaults) # If interface does not request an IPv4 DHCP address there is no need # to keep the dhcp-options key @@ -452,8 +429,12 @@ def get_interface_dict(config, base, ifname=''): if 'dhcp_options' in dict: del dict['dhcp_options'] - # XXX: T2665: blend in proper DHCPv6-PD default values - dict = T2665_set_dhcpv6pd_defaults(dict) + # Add interface instance name into dictionary + dict.update({'ifname': ifname}) + + # Check if QoS policy applied on this interface - See ifconfig.interface.set_mirror_redirect() + if config.exists(['qos', 'interface', ifname]): + dict.update({'traffic_policy': {}}) address = leaf_node_changed(config, base + [ifname, 'address']) if address: dict.update({'address_old' : address}) @@ -497,9 +478,6 @@ def get_interface_dict(config, base, ifname=''): else: dict['ipv6']['address'].update({'eui64_old': eui64}) - # Implant default dictionary in vif/vif-s VLAN interfaces. Values are - # identical for all types of VLAN interfaces as they all include the same - # XML definitions which hold the defaults. for vif, vif_config in dict.get('vif', {}).items(): # Add subinterface name to dictionary dict['vif'][vif].update({'ifname' : f'{ifname}.{vif}'}) @@ -507,22 +485,10 @@ def get_interface_dict(config, base, ifname=''): if config.exists(['qos', 'interface', f'{ifname}.{vif}']): dict['vif'][vif].update({'traffic_policy': {}}) - default_vif_values = defaults(base + ['vif']) - # XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely - # remove the default values from the dict. - if not 'dhcpv6_options' in vif_config: - del default_vif_values['dhcpv6_options'] - - # Only add defaults if interface is not about to be deleted - this is - # to keep a cleaner config dict. if 'deleted' not in dict: address = leaf_node_changed(config, base + [ifname, 'vif', vif, 'address']) if address: dict['vif'][vif].update({'address_old' : address}) - dict['vif'][vif] = dict_merge(default_vif_values, dict['vif'][vif]) - # XXX: T2665: blend in proper DHCPv6-PD default values - dict['vif'][vif] = T2665_set_dhcpv6pd_defaults(dict['vif'][vif]) - # If interface does not request an IPv4 DHCP address there is no need # to keep the dhcp-options key if 'address' not in dict['vif'][vif] or 'dhcp' not in dict['vif'][vif]['address']: @@ -544,26 +510,10 @@ def get_interface_dict(config, base, ifname=''): if config.exists(['qos', 'interface', f'{ifname}.{vif_s}']): dict['vif_s'][vif_s].update({'traffic_policy': {}}) - default_vif_s_values = defaults(base + ['vif-s']) - # XXX: T2665: we only wan't the vif-s defaults - do not care about vif-c - if 'vif_c' in default_vif_s_values: del default_vif_s_values['vif_c'] - - # XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely - # remove the default values from the dict. - if not 'dhcpv6_options' in vif_s_config: - del default_vif_s_values['dhcpv6_options'] - - # Only add defaults if interface is not about to be deleted - this is - # to keep a cleaner config dict. if 'deleted' not in dict: address = leaf_node_changed(config, base + [ifname, 'vif-s', vif_s, 'address']) if address: dict['vif_s'][vif_s].update({'address_old' : address}) - dict['vif_s'][vif_s] = dict_merge(default_vif_s_values, - dict['vif_s'][vif_s]) - # XXX: T2665: blend in proper DHCPv6-PD default values - dict['vif_s'][vif_s] = T2665_set_dhcpv6pd_defaults(dict['vif_s'][vif_s]) - # If interface does not request an IPv4 DHCP address there is no need # to keep the dhcp-options key if 'address' not in dict['vif_s'][vif_s] or 'dhcp' not in \ @@ -586,26 +536,11 @@ def get_interface_dict(config, base, ifname=''): if config.exists(['qos', 'interface', f'{ifname}.{vif_s}.{vif_c}']): dict['vif_s'][vif_s]['vif_c'][vif_c].update({'traffic_policy': {}}) - default_vif_c_values = defaults(base + ['vif-s', 'vif-c']) - - # XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely - # remove the default values from the dict. - if not 'dhcpv6_options' in vif_c_config: - del default_vif_c_values['dhcpv6_options'] - - # Only add defaults if interface is not about to be deleted - this is - # to keep a cleaner config dict. if 'deleted' not in dict: address = leaf_node_changed(config, base + [ifname, 'vif-s', vif_s, 'vif-c', vif_c, 'address']) if address: dict['vif_s'][vif_s]['vif_c'][vif_c].update( {'address_old' : address}) - dict['vif_s'][vif_s]['vif_c'][vif_c] = dict_merge( - default_vif_c_values, dict['vif_s'][vif_s]['vif_c'][vif_c]) - # XXX: T2665: blend in proper DHCPv6-PD default values - dict['vif_s'][vif_s]['vif_c'][vif_c] = T2665_set_dhcpv6pd_defaults( - dict['vif_s'][vif_s]['vif_c'][vif_c]) - # If interface does not request an IPv4 DHCP address there is no need # to keep the dhcp-options key if 'address' not in dict['vif_s'][vif_s]['vif_c'][vif_c] or 'dhcp' \ diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index df44fd8d6..decb82437 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -1,5 +1,5 @@ # configsession -- the write API for the VyOS running config -# Copyright (C) 2019 VyOS maintainers and contributors +# Copyright (C) 2019-2023 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; @@ -18,6 +18,7 @@ import sys import subprocess from vyos.util import is_systemd_service_running +from vyos.utils.dict import dict_to_paths CLI_SHELL_API = '/bin/cli-shell-api' SET = '/opt/vyatta/sbin/my_set' @@ -148,6 +149,13 @@ class ConfigSession(object): value = [value] self.__run_command([SET] + path + value) + def set_section(self, path: list, d: dict): + try: + for p in dict_to_paths(d): + self.set(path + p) + except (ValueError, ConfigSessionError) as e: + raise ConfigSessionError(e) + def delete(self, path, value=None): if not value: value = [] @@ -155,6 +163,15 @@ class ConfigSession(object): value = [value] self.__run_command([DELETE] + path + value) + def load_section(self, path: list, d: dict): + try: + self.delete(path) + if d: + for p in dict_to_paths(d): + self.set(path + p) + except (ValueError, ConfigSessionError) as e: + raise ConfigSessionError(e) + def comment(self, path, value=None): if not value: value = [""] diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py index 19b9838d4..d0cd87464 100644 --- a/python/vyos/configtree.py +++ b/python/vyos/configtree.py @@ -201,7 +201,9 @@ class ConfigTree(object): check_path(path) path_str = " ".join(map(str, path)).encode() - self.__delete(self.__config, path_str) + res = self.__delete(self.__config, path_str) + if (res != 0): + raise ConfigTreeError(f"Path doesn't exist: {path}") if self.__migration: print(f"- op: delete path: {path}") @@ -210,7 +212,14 @@ class ConfigTree(object): check_path(path) path_str = " ".join(map(str, path)).encode() - self.__delete_value(self.__config, path_str, value.encode()) + res = self.__delete_value(self.__config, path_str, value.encode()) + if (res != 0): + if res == 1: + raise ConfigTreeError(f"Path doesn't exist: {path}") + elif res == 2: + raise ConfigTreeError(f"Value doesn't exist: '{value}'") + else: + raise ConfigTreeError() if self.__migration: print(f"- op: delete_value path: {path} value: {value}") diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 8fddd91d0..94dcdf4d9 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -322,7 +322,7 @@ def verify_dhcpv6(config): # It is not allowed to have duplicate SLA-IDs as those identify an # assigned IPv6 subnet from a delegated prefix - for pd in dict_search('dhcpv6_options.pd', config): + for pd in (dict_search('dhcpv6_options.pd', config) or []): sla_ids = [] interfaces = dict_search(f'dhcpv6_options.pd.{pd}.interface', config) diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index 1b1e54dfb..68234089c 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -21,7 +21,7 @@ from vyos.util import popen # These drivers do not support using ethtool to change the speed, duplex, or # flow control settings _drivers_without_speed_duplex_flow = ['vmxnet3', 'virtio_net', 'xen_netfront', - 'iavf', 'ice', 'i40e', 'hv_netvsc', 'veth'] + 'iavf', 'ice', 'i40e', 'hv_netvsc', 'veth', 'ixgbevf'] class Ethtool: """ diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index f62b9f7d2..f6289a6e6 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -57,6 +57,8 @@ from vyos.ifconfig import Section from netaddr import EUI from netaddr import mac_unix_expanded +link_local_prefix = 'fe80::/64' + class Interface(Control): # This is the class which will be used to create # self.operational, it allows subclasses, such as @@ -1353,34 +1355,6 @@ class Interface(Control): f'egress redirect dev {target_if}') if err: print('tc filter add for redirect failed') - def set_xdp(self, state): - """ - Enable Kernel XDP support. State can be either True or False. - - Example: - >>> from vyos.ifconfig import Interface - >>> i = Interface('eth0') - >>> i.set_xdp(True) - """ - if not isinstance(state, bool): - raise ValueError("Value out of range") - - # https://vyos.dev/T3448 - there is (yet) no RPI support for XDP - if not os.path.exists('/usr/sbin/xdp_loader'): - return - - ifname = self.config['ifname'] - cmd = f'xdp_loader -d {ifname} -U --auto-mode' - if state: - # Using 'xdp' will automatically decide if the driver supports - # 'xdpdrv' or only 'xdpgeneric'. A user later sees which driver is - # actually in use by calling 'ip a' or 'show interfaces ethernet' - cmd = f'xdp_loader -d {ifname} --auto-mode -F --progsec xdp_router ' \ - f'--filename /usr/share/vyos/xdp/xdp_prog_kern.o && ' \ - f'xdp_prog_user -d {ifname}' - - return self._cmd(cmd) - 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 @@ -1444,7 +1418,7 @@ class Interface(Control): # we will delete all interface specific IP addresses if they are not # explicitly configured on the CLI if is_ipv6_link_local(addr): - eui64 = mac2eui64(self.get_mac(), 'fe80::/64') + eui64 = mac2eui64(self.get_mac(), link_local_prefix) if addr != f'{eui64}/64': self.del_addr(addr) else: @@ -1571,9 +1545,9 @@ class Interface(Control): # Manage IPv6 link-local addresses if dict_search('ipv6.address.no_default_link_local', config) != None: - self.del_ipv6_eui64_address('fe80::/64') + self.del_ipv6_eui64_address(link_local_prefix) else: - self.add_ipv6_eui64_address('fe80::/64') + self.add_ipv6_eui64_address(link_local_prefix) # Add IPv6 EUI-based addresses tmp = dict_search('ipv6.address.eui64', config) @@ -1586,9 +1560,6 @@ class Interface(Control): tmp = config.get('is_bridge_member') self.add_to_bridge(tmp) - # eXpress Data Path - highly experimental - self.set_xdp('xdp' in config) - # configure interface mirror or redirection target self.set_mirror_redirect() diff --git a/python/vyos/qos/base.py b/python/vyos/qos/base.py index 33bb8ae28..26ec65535 100644 --- a/python/vyos/qos/base.py +++ b/python/vyos/qos/base.py @@ -1,4 +1,4 @@ -# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2022-2023 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 @@ -20,10 +20,47 @@ from vyos.util import cmd from vyos.util import dict_search from vyos.util import read_file +from vyos.utils.network import get_protocol_by_name + + class QoSBase: _debug = False _direction = ['egress'] _parent = 0xffff + _dsfields = { + "default": 0x0, + "lowdelay": 0x10, + "throughput": 0x08, + "reliability": 0x04, + "mincost": 0x02, + "priority": 0x20, + "immediate": 0x40, + "flash": 0x60, + "flash-override": 0x80, + "critical": 0x0A, + "internet": 0xC0, + "network": 0xE0, + "AF11": 0x28, + "AF12": 0x30, + "AF13": 0x38, + "AF21": 0x48, + "AF22": 0x50, + "AF23": 0x58, + "AF31": 0x68, + "AF32": 0x70, + "AF33": 0x78, + "AF41": 0x88, + "AF42": 0x90, + "AF43": 0x98, + "CS1": 0x20, + "CS2": 0x40, + "CS3": 0x60, + "CS4": 0x80, + "CS5": 0xA0, + "CS6": 0xC0, + "CS7": 0xE0, + "EF": 0xB8 + } def __init__(self, interface): if os.path.exists('/tmp/vyos.qos.debug'): @@ -45,6 +82,12 @@ class QoSBase: return tmp[-1] return None + def _get_dsfield(self, value): + if value in self._dsfields: + return self._dsfields[value] + else: + return value + def _build_base_qdisc(self, config : dict, cls_id : int): """ Add/replace qdisc for every class (also default is a class). This is @@ -197,7 +240,17 @@ class QoSBase: if tmp: filter_cmd += f' match {tc_af} dport {tmp} 0xffff' tmp = dict_search(f'{af}.protocol', match_config) - if tmp: filter_cmd += f' match {tc_af} protocol {tmp} 0xff' + if tmp: + tmp = get_protocol_by_name(tmp) + filter_cmd += f' match {tc_af} protocol {tmp} 0xff' + + tmp = dict_search(f'{af}.dscp', match_config) + if tmp: + tmp = self._get_dsfield(tmp) + if af == 'ip': + filter_cmd += f' match {tc_af} dsfield {tmp} 0xff' + elif af == 'ipv6': + filter_cmd += f' match u16 {tmp} 0x0ff0 at 0' # Will match against total length of an IPv4 packet and # payload length of an IPv6 packet. @@ -243,60 +296,70 @@ class QoSBase: # The police block allows limiting of the byte or packet rate of # traffic matched by the filter it is attached to. # https://man7.org/linux/man-pages/man8/tc-police.8.html - if any(tmp in ['exceed', 'bandwidth', 'burst'] for tmp in cls_config): - filter_cmd += f' action police' - - if 'exceed' in cls_config: - action = cls_config['exceed'] - filter_cmd += f' conform-exceed {action}' - if 'not_exceed' in cls_config: - action = cls_config['not_exceed'] - filter_cmd += f'/{action}' - if 'bandwidth' in cls_config: - rate = self._rate_convert(cls_config['bandwidth']) - filter_cmd += f' rate {rate}' - - if 'burst' in cls_config: - burst = cls_config['burst'] - filter_cmd += f' burst {burst}' + # T5295: We do not handle rate via tc filter directly, + # but rather set the tc filter to direct traffic to the correct tc class flow. + # + # if any(tmp in ['exceed', 'bandwidth', 'burst'] for tmp in cls_config): + # filter_cmd += f' action police' + # + # if 'exceed' in cls_config: + # action = cls_config['exceed'] + # filter_cmd += f' conform-exceed {action}' + # if 'not_exceed' in cls_config: + # action = cls_config['not_exceed'] + # filter_cmd += f'/{action}' + # + # if 'bandwidth' in cls_config: + # rate = self._rate_convert(cls_config['bandwidth']) + # filter_cmd += f' rate {rate}' + # + # if 'burst' in cls_config: + # burst = cls_config['burst'] + # filter_cmd += f' burst {burst}' cls = int(cls) filter_cmd += f' flowid {self._parent:x}:{cls:x}' self._cmd(filter_cmd) - if 'default' in config: - if 'class' in config: - class_id_max = self._get_class_max_id(config) - default_cls_id = int(class_id_max) +1 - self._build_base_qdisc(config['default'], default_cls_id) - - filter_cmd = f'tc filter replace dev {self._interface} parent {self._parent:x}: ' - filter_cmd += 'prio 255 protocol all basic' - - # The police block allows limiting of the byte or packet rate of - # traffic matched by the filter it is attached to. - # https://man7.org/linux/man-pages/man8/tc-police.8.html - if any(tmp in ['exceed', 'bandwidth', 'burst'] for tmp in config['default']): - filter_cmd += f' action police' - - if 'exceed' in config['default']: - action = config['default']['exceed'] - filter_cmd += f' conform-exceed {action}' - if 'not_exceed' in config['default']: - action = config['default']['not_exceed'] - filter_cmd += f'/{action}' - - if 'bandwidth' in config['default']: - rate = self._rate_convert(config['default']['bandwidth']) - filter_cmd += f' rate {rate}' - - if 'burst' in config['default']: - burst = config['default']['burst'] - filter_cmd += f' burst {burst}' - - if 'class' in config: - filter_cmd += f' flowid {self._parent:x}:{default_cls_id:x}' - - self._cmd(filter_cmd) - + # T5295: Do not do any tc filter action for 'default' + # In VyOS 1.4, we have the following configuration: + # tc filter replace dev eth0 parent 1: prio 255 protocol all basic action police rate 300000000 burst 15k + # However, this caused unexpected random speeds. + # In VyOS 1.3, we do not use any 'tc filter' for rate limits, + # It gets rate from tc class classid 1:1 + # + # if 'default' in config: + # if 'class' in config: + # class_id_max = self._get_class_max_id(config) + # default_cls_id = int(class_id_max) +1 + # self._build_base_qdisc(config['default'], default_cls_id) + # + # filter_cmd = f'tc filter replace dev {self._interface} parent {self._parent:x}: ' + # filter_cmd += 'prio 255 protocol all basic' + # + # # The police block allows limiting of the byte or packet rate of + # # traffic matched by the filter it is attached to. + # # https://man7.org/linux/man-pages/man8/tc-police.8.html + # if any(tmp in ['exceed', 'bandwidth', 'burst'] for tmp in config['default']): + # filter_cmd += f' action police' + # + # if 'exceed' in config['default']: + # action = config['default']['exceed'] + # filter_cmd += f' conform-exceed {action}' + # if 'not_exceed' in config['default']: + # action = config['default']['not_exceed'] + # filter_cmd += f'/{action}' + # + # if 'bandwidth' in config['default']: + # rate = self._rate_convert(config['default']['bandwidth']) + # filter_cmd += f' rate {rate}' + # + # if 'burst' in config['default']: + # burst = config['default']['burst'] + # filter_cmd += f' burst {burst}' + # + # if 'class' in config: + # filter_cmd += f' flowid {self._parent:x}:{default_cls_id:x}' + # + # self._cmd(filter_cmd) diff --git a/python/vyos/qos/trafficshaper.py b/python/vyos/qos/trafficshaper.py index f42f4d022..573283833 100644 --- a/python/vyos/qos/trafficshaper.py +++ b/python/vyos/qos/trafficshaper.py @@ -70,7 +70,17 @@ class TrafficShaper(QoSBase): cls = int(cls) # bandwidth is a mandatory CLI node - rate = self._rate_convert(cls_config['bandwidth']) + # T5296 if bandwidth 'auto' or 'xx%' get value from config shaper total "bandwidth" + # i.e from set shaper test bandwidth '300mbit' + # without it, it tries to get value from qos.base /sys/class/net/{self._interface}/speed + if cls_config['bandwidth'] == 'auto': + rate = self._rate_convert(config['bandwidth']) + elif cls_config['bandwidth'].endswith('%'): + percent = cls_config['bandwidth'].rstrip('%') + rate = self._rate_convert(config['bandwidth']) * int(percent) // 100 + else: + rate = self._rate_convert(cls_config['bandwidth']) + burst = cls_config['burst'] quantum = cls_config['codel_quantum'] diff --git a/python/vyos/util.py b/python/vyos/util.py index d5330db13..e62f9d5cf 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -463,14 +463,17 @@ def process_running(pid_file): pid = f.read().strip() return pid_exists(int(pid)) -def process_named_running(name): +def process_named_running(name, cmdline: str=None): """ Checks if process with given name is running and returns its PID. If Process is not running, return None """ from psutil import process_iter - for p in process_iter(): - if name in p.name(): - return p.pid + for p in process_iter(['name', 'pid', 'cmdline']): + if cmdline: + if p.info['name'] == name and cmdline in p.info['cmdline']: + return p.info['pid'] + elif p.info['name'] == name: + return p.info['pid'] return None def is_list_equal(first: list, second: list) -> bool: @@ -1060,9 +1063,13 @@ def check_port_availability(ipaddress, port, protocol): if protocol == 'udp': server = UDPServer((ipaddress, port), None, bind_and_activate=True) server.server_close() - return True - except: - return False + except Exception as e: + # errno.h: + #define EADDRINUSE 98 /* Address already in use */ + if e.errno == 98: + return False + + return True def install_into_config(conf, config_paths, override_prompt=True): # Allows op-mode scripts to install values if called from an active config session diff --git a/python/vyos/utils/__init__.py b/python/vyos/utils/__init__.py index e69de29bb..0d3998053 100644 --- a/python/vyos/utils/__init__.py +++ b/python/vyos/utils/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2023 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 +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# 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.utils import network diff --git a/python/vyos/utils/dict.py b/python/vyos/utils/dict.py index 8fb519a5c..28d32bb8d 100644 --- a/python/vyos/utils/dict.py +++ b/python/vyos/utils/dict.py @@ -229,6 +229,27 @@ def dict_to_list(d, save_key_to=None): return collect +def dict_to_paths(d: dict) -> list: + """ Generator to return list of paths from dict of list[str]|str + """ + def func(d, path): + if isinstance(d, dict): + if not d: + yield path + for k, v in d.items(): + for r in func(v, path + [k]): + yield r + elif isinstance(d, list): + for i in d: + for r in func(i, path): + yield r + elif isinstance(d, str): + yield path + [d] + else: + raise ValueError('object is not a dict of strings/list of strings') + for r in func(d, []): + yield r + def check_mutually_exclusive_options(d, keys, required=False): """ Checks if a dict has at most one or only one of mutually exclusive keys. diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py new file mode 100644 index 000000000..72b7ca6da --- /dev/null +++ b/python/vyos/utils/network.py @@ -0,0 +1,30 @@ +# Copyright 2023 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 +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# 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 os + + +def get_protocol_by_name(protocol_name): + """Get protocol number by protocol name + + % get_protocol_by_name('tcp') + % 6 + """ + import socket + try: + protocol_number = socket.getprotobyname(protocol_name) + return protocol_number + except socket.error: + return protocol_name diff --git a/python/vyos/validate.py b/python/vyos/validate.py index a83193363..d18785aaf 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -62,7 +62,7 @@ def is_intf_addr_assigned(intf, address) -> bool: # 10: [{'addr': 'fe80::a00:27ff:fed9:5b04%eth0', 'netmask': 'ffff:ffff:ffff:ffff::'}] # } try: - ifaces = ifaddresses(intf) + addresses = ifaddresses(intf) except ValueError as e: print(e) return False @@ -74,7 +74,7 @@ def is_intf_addr_assigned(intf, address) -> bool: netmask = None if '/' in address: address, netmask = address.split('/') - for ip in ifaces.get(addr_type,[]): + for ip in addresses.get(addr_type, []): # ip can have the interface name in the 'addr' field, we need to remove it # {'addr': 'fe80::a00:27ff:fec5:f821%eth2', 'netmask': 'ffff:ffff:ffff:ffff::'} ip_addr = ip['addr'].split('%')[0] diff --git a/python/vyos/xml_ref/__init__.py b/python/vyos/xml_ref/__init__.py index 53ca6ed98..62d3680a1 100644 --- a/python/vyos/xml_ref/__init__.py +++ b/python/vyos/xml_ref/__init__.py @@ -45,6 +45,9 @@ def is_valueless(path: list) -> bool: def is_leaf(path: list) -> bool: return load_reference().is_leaf(path) +def cli_defined(path: list, node: str, non_local=False) -> bool: + return load_reference().cli_defined(path, node, non_local=non_local) + def component_version() -> dict: return load_reference().component_version() diff --git a/python/vyos/xml_ref/definition.py b/python/vyos/xml_ref/definition.py index 92d069f05..7fd7a7b77 100644 --- a/python/vyos/xml_ref/definition.py +++ b/python/vyos/xml_ref/definition.py @@ -91,6 +91,31 @@ class Xml: d = self._get_ref_path(path) return self._is_leaf_node(d) + @staticmethod + def _dict_get(d: dict, path: list) -> dict: + for i in path: + d = d.get(i, {}) + if not isinstance(d, dict): + return {} + if not d: + break + return d + + def _dict_find(self, d: dict, key: str, non_local=False) -> bool: + for k in list(d): + if k in ('node_data', 'component_version'): + continue + if k == key: + return True + if non_local and isinstance(d[k], dict): + if self._dict_find(d[k], key): + return True + return False + + def cli_defined(self, path: list, node: str, non_local=False) -> bool: + d = self._dict_get(self.ref, path) + return self._dict_find(d, node, non_local=non_local) + def component_version(self) -> dict: d = {} for k, v in self.ref['component_version']: |