summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/vyos/config_mgmt.py57
-rw-r--r--python/vyos/configdict.py99
-rw-r--r--python/vyos/configsession.py19
-rw-r--r--python/vyos/configtree.py13
-rw-r--r--python/vyos/configverify.py2
-rw-r--r--python/vyos/ethtool.py2
-rw-r--r--python/vyos/ifconfig/interface.py39
-rw-r--r--python/vyos/qos/base.py169
-rw-r--r--python/vyos/qos/trafficshaper.py12
-rw-r--r--python/vyos/util.py21
-rw-r--r--python/vyos/utils/__init__.py16
-rw-r--r--python/vyos/utils/dict.py21
-rw-r--r--python/vyos/utils/network.py30
-rw-r--r--python/vyos/validate.py4
-rw-r--r--python/vyos/xml_ref/__init__.py3
-rw-r--r--python/vyos/xml_ref/definition.py25
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']: