summaryrefslogtreecommitdiff
path: root/python/vyos
diff options
context:
space:
mode:
Diffstat (limited to 'python/vyos')
-rw-r--r--python/vyos/configdiff.py31
-rw-r--r--python/vyos/configsession.py14
-rw-r--r--python/vyos/ifconfig/ethernet.py12
-rw-r--r--python/vyos/ifconfig/input.py12
-rw-r--r--python/vyos/opmode.py20
-rw-r--r--python/vyos/qos/__init__.py28
-rw-r--r--python/vyos/qos/base.py282
-rw-r--r--python/vyos/qos/cake.py55
-rw-r--r--python/vyos/qos/droptail.py28
-rw-r--r--python/vyos/qos/fairqueue.py31
-rw-r--r--python/vyos/qos/fqcodel.py40
-rw-r--r--python/vyos/qos/limiter.py27
-rw-r--r--python/vyos/qos/netem.py53
-rw-r--r--python/vyos/qos/priority.py41
-rw-r--r--python/vyos/qos/randomdetect.py54
-rw-r--r--python/vyos/qos/ratelimiter.py37
-rw-r--r--python/vyos/qos/roundrobin.py44
-rw-r--r--python/vyos/qos/trafficshaper.py106
-rw-r--r--python/vyos/util.py6
19 files changed, 897 insertions, 24 deletions
diff --git a/python/vyos/configdiff.py b/python/vyos/configdiff.py
index 9185575df..ac86af09c 100644
--- a/python/vyos/configdiff.py
+++ b/python/vyos/configdiff.py
@@ -78,23 +78,34 @@ def get_config_diff(config, key_mangling=None):
isinstance(key_mangling[1], str)):
raise ValueError("key_mangling must be a tuple of two strings")
- diff_t = DiffTree(config._running_config, config._session_config)
+ if hasattr(config, 'cached_diff_tree'):
+ diff_t = getattr(config, 'cached_diff_tree')
+ else:
+ diff_t = DiffTree(config._running_config, config._session_config)
+ setattr(config, 'cached_diff_tree', diff_t)
- return ConfigDiff(config, key_mangling, diff_tree=diff_t)
+ if hasattr(config, 'cached_diff_dict'):
+ diff_d = getattr(config, 'cached_diff_dict')
+ else:
+ diff_d = diff_t.dict
+ setattr(config, 'cached_diff_dict', diff_d)
+
+ return ConfigDiff(config, key_mangling, diff_tree=diff_t,
+ diff_dict=diff_d)
class ConfigDiff(object):
"""
The class of config changes as represented by comparison between the
session config dict and the effective config dict.
"""
- def __init__(self, config, key_mangling=None, diff_tree=None):
+ def __init__(self, config, key_mangling=None, diff_tree=None, diff_dict=None):
self._level = config.get_level()
self._session_config_dict = config.get_cached_root_dict(effective=False)
self._effective_config_dict = config.get_cached_root_dict(effective=True)
self._key_mangling = key_mangling
self._diff_tree = diff_tree
- self._diff_dict = diff_tree.dict if diff_tree else {}
+ self._diff_dict = diff_dict
# mirrored from Config; allow path arguments relative to level
def _make_path(self, path):
@@ -209,9 +220,9 @@ class ConfigDiff(object):
if self._diff_tree is None:
raise NotImplementedError("diff_tree class not available")
else:
- add = get_sub_dict(self._diff_tree.dict, ['add'], get_first_key=True)
- sub = get_sub_dict(self._diff_tree.dict, ['sub'], get_first_key=True)
- inter = get_sub_dict(self._diff_tree.dict, ['inter'], get_first_key=True)
+ add = get_sub_dict(self._diff_dict, ['add'], get_first_key=True)
+ sub = get_sub_dict(self._diff_dict, ['sub'], get_first_key=True)
+ inter = get_sub_dict(self._diff_dict, ['inter'], get_first_key=True)
ret = {}
ret[enum_to_key(Diff.MERGE)] = session_dict
ret[enum_to_key(Diff.DELETE)] = get_sub_dict(sub, self._make_path(path),
@@ -284,9 +295,9 @@ class ConfigDiff(object):
if self._diff_tree is None:
raise NotImplementedError("diff_tree class not available")
else:
- add = get_sub_dict(self._diff_tree.dict, ['add'], get_first_key=True)
- sub = get_sub_dict(self._diff_tree.dict, ['sub'], get_first_key=True)
- inter = get_sub_dict(self._diff_tree.dict, ['inter'], get_first_key=True)
+ add = get_sub_dict(self._diff_dict, ['add'], get_first_key=True)
+ sub = get_sub_dict(self._diff_dict, ['sub'], get_first_key=True)
+ inter = get_sub_dict(self._diff_dict, ['inter'], get_first_key=True)
ret = {}
ret[enum_to_key(Diff.MERGE)] = session_dict
ret[enum_to_key(Diff.DELETE)] = get_sub_dict(sub, self._make_path(path))
diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py
index 3a60f6d92..df44fd8d6 100644
--- a/python/vyos/configsession.py
+++ b/python/vyos/configsession.py
@@ -34,6 +34,8 @@ REMOVE_IMAGE = ['/opt/vyatta/bin/vyatta-boot-image.pl', '--del']
GENERATE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'generate']
SHOW = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'show']
RESET = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'reset']
+OP_CMD_ADD = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'add']
+OP_CMD_DELETE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'delete']
# Default "commit via" string
APP = "vyos-http-api"
@@ -204,3 +206,15 @@ class ConfigSession(object):
def reset(self, path):
out = self.__run_command(RESET + path)
return out
+
+ def add_container_image(self, name):
+ out = self.__run_command(OP_CMD_ADD + ['container', 'image'] + [name])
+ return out
+
+ def delete_container_image(self, name):
+ out = self.__run_command(OP_CMD_DELETE + ['container', 'image'] + [name])
+ return out
+
+ def show_container_image(self):
+ out = self.__run_command(SHOW + ['container', 'image'])
+ return out
diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py
index 519cfc58c..5080144ff 100644
--- a/python/vyos/ifconfig/ethernet.py
+++ b/python/vyos/ifconfig/ethernet.py
@@ -239,7 +239,7 @@ class EthernetIf(Interface):
if not isinstance(state, bool):
raise ValueError('Value out of range')
- rps_cpus = '0'
+ rps_cpus = 0
queues = len(glob(f'/sys/class/net/{self.ifname}/queues/rx-*'))
if state:
# Enable RPS on all available CPUs except CPU0 which we will not
@@ -248,10 +248,16 @@ class EthernetIf(Interface):
# representation of the CPUs which should participate on RPS, we
# can enable more CPUs that are physically present on the system,
# Linux will clip that internally!
- rps_cpus = 'ffffffff,ffffffff,ffffffff,fffffffe'
+ rps_cpus = (1 << os.cpu_count()) -1
+
+ # XXX: we should probably reserve one core when the system is under
+ # high preasure so we can still have a core left for housekeeping.
+ # This is done by masking out the lowst bit so CPU0 is spared from
+ # receive packet steering.
+ rps_cpus &= ~1
for i in range(0, queues):
- self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_cpus', rps_cpus)
+ self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_cpus', f'{rps_cpus:x}')
# send bitmask representation as hex string without leading '0x'
return True
diff --git a/python/vyos/ifconfig/input.py b/python/vyos/ifconfig/input.py
index db7d2b6b4..3e5f5790d 100644
--- a/python/vyos/ifconfig/input.py
+++ b/python/vyos/ifconfig/input.py
@@ -1,4 +1,4 @@
-# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io>
+# 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
@@ -17,6 +17,16 @@ from vyos.ifconfig.interface import Interface
@Interface.register
class InputIf(Interface):
+ """
+ The Intermediate Functional Block (ifb) pseudo network interface acts as a
+ QoS concentrator for multiple different sources of traffic. Packets from
+ or to other interfaces have to be redirected to it using the mirred action
+ in order to be handled, regularly routed traffic will be dropped. This way,
+ a single stack of qdiscs, classes and filters can be shared between
+ multiple interfaces.
+ """
+
+ iftype = 'ifb'
definition = {
**Interface.definition,
**{
diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py
index 5ff768859..30e893d74 100644
--- a/python/vyos/opmode.py
+++ b/python/vyos/opmode.py
@@ -45,6 +45,10 @@ class PermissionDenied(Error):
"""
pass
+class UnsupportedOperation(Error):
+ """ Requested operation is technically valid but is not implemented yet. """
+ pass
+
class IncorrectValue(Error):
""" Requested operation is valid, but an argument provided has an
incorrect value, preventing successful completion.
@@ -66,13 +70,13 @@ class InternalError(Error):
def _is_op_mode_function_name(name):
- if re.match(r"^(show|clear|reset|restart)", name):
+ if re.match(r"^(show|clear|reset|restart|add|delete|generate)", name):
return True
else:
return False
-def _is_show(name):
- if re.match(r"^show", name):
+def _capture_output(name):
+ if re.match(r"^(show|generate)", name):
return True
else:
return False
@@ -199,14 +203,14 @@ def run(module):
# it would cause an extra argument error when we pass the dict to a function
del args["subcommand"]
- # Show commands must always get the "raw" argument,
- # but other commands (clear/reset/restart) should not,
+ # Show and generate commands must always get the "raw" argument,
+ # but other commands (clear/reset/restart/add/delete) should not,
# because they produce no output and it makes no sense for them.
- if ("raw" not in args) and _is_show(function_name):
+ if ("raw" not in args) and _capture_output(function_name):
args["raw"] = False
- if re.match(r"^show", function_name):
- # Show commands are slightly special:
+ if _capture_output(function_name):
+ # Show and generate commands are slightly special:
# they may return human-formatted output
# or a raw dict that we need to serialize in JSON for printing
res = func(**args)
diff --git a/python/vyos/qos/__init__.py b/python/vyos/qos/__init__.py
new file mode 100644
index 000000000..a2980ccde
--- /dev/null
+++ b/python/vyos/qos/__init__.py
@@ -0,0 +1,28 @@
+# Copyright 2022 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.qos.base import QoSBase
+from vyos.qos.cake import CAKE
+from vyos.qos.droptail import DropTail
+from vyos.qos.fairqueue import FairQueue
+from vyos.qos.fqcodel import FQCodel
+from vyos.qos.limiter import Limiter
+from vyos.qos.netem import NetEm
+from vyos.qos.priority import Priority
+from vyos.qos.randomdetect import RandomDetect
+from vyos.qos.ratelimiter import RateLimiter
+from vyos.qos.roundrobin import RoundRobin
+from vyos.qos.trafficshaper import TrafficShaper
+from vyos.qos.trafficshaper import TrafficShaperHFSC
diff --git a/python/vyos/qos/base.py b/python/vyos/qos/base.py
new file mode 100644
index 000000000..28635b5e7
--- /dev/null
+++ b/python/vyos/qos/base.py
@@ -0,0 +1,282 @@
+# Copyright 2022 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
+
+from vyos.base import Warning
+from vyos.util import cmd
+from vyos.util import dict_search
+from vyos.util import read_file
+
+class QoSBase:
+ _debug = False
+ _direction = ['egress']
+ _parent = 0xffff
+
+ def __init__(self, interface):
+ if os.path.exists('/tmp/vyos.qos.debug'):
+ self._debug = True
+ self._interface = interface
+
+ def _cmd(self, command):
+ if self._debug:
+ print(f'DEBUG/QoS: {command}')
+ return cmd(command)
+
+ def get_direction(self) -> list:
+ return self._direction
+
+ def _get_class_max_id(self, config) -> int:
+ if 'class' in config:
+ tmp = list(config['class'].keys())
+ tmp.sort(key=lambda ii: int(ii))
+ return tmp[-1]
+ return None
+
+ def _build_base_qdisc(self, config : dict, cls_id : int):
+ """
+ Add/replace qdisc for every class (also default is a class). This is
+ a genetic method which need an implementation "per" queue-type.
+
+ This matches the old mapping as defined in Perl here:
+ https://github.com/vyos/vyatta-cfg-qos/blob/equuleus/lib/Vyatta/Qos/ShaperClass.pm#L223-L229
+ """
+ queue_type = dict_search('queue_type', config)
+ default_tc = f'tc qdisc replace dev {self._interface} parent {self._parent}:{cls_id:x}'
+
+ if queue_type == 'priority':
+ handle = 0x4000 + cls_id
+ default_tc += f' handle {handle:x}: prio'
+ self._cmd(default_tc)
+
+ queue_limit = dict_search('queue_limit', config)
+ for ii in range(1, 4):
+ tmp = f'tc qdisc replace dev {self._interface} parent {handle:x}:{ii:x} pfifo limit {queue_limit}'
+ self._cmd(tmp)
+
+ elif queue_type == 'fair-queue':
+ default_tc += f' sfq'
+
+ tmp = dict_search('queue_limit', config)
+ if tmp: default_tc += f' limit {tmp}'
+
+ self._cmd(default_tc)
+
+ elif queue_type == 'fq-codel':
+ default_tc += f' fq_codel'
+ tmp = dict_search('codel_quantum', config)
+ if tmp: default_tc += f' quantum {tmp}'
+
+ tmp = dict_search('flows', config)
+ if tmp: default_tc += f' flows {tmp}'
+
+ tmp = dict_search('interval', config)
+ if tmp: default_tc += f' interval {tmp}'
+
+ tmp = dict_search('interval', config)
+ if tmp: default_tc += f' interval {tmp}'
+
+ tmp = dict_search('queue_limit', config)
+ if tmp: default_tc += f' limit {tmp}'
+
+ tmp = dict_search('target', config)
+ if tmp: default_tc += f' target {tmp}'
+
+ default_tc += f' noecn'
+
+ self._cmd(default_tc)
+
+ elif queue_type == 'random-detect':
+ default_tc += f' red'
+
+ self._cmd(default_tc)
+
+ elif queue_type == 'drop-tail':
+ default_tc += f' pfifo'
+
+ tmp = dict_search('queue_limit', config)
+ if tmp: default_tc += f' limit {tmp}'
+
+ self._cmd(default_tc)
+
+ def _rate_convert(self, rate) -> int:
+ rates = {
+ 'bit' : 1,
+ 'kbit' : 1000,
+ 'mbit' : 1000000,
+ 'gbit' : 1000000000,
+ 'tbit' : 1000000000000,
+ }
+
+ if rate == 'auto' or rate.endswith('%'):
+ speed = read_file(f'/sys/class/net/{self._interface}/speed')
+ if not speed.isnumeric():
+ Warning('Interface speed cannot be determined (assuming 10 Mbit/s)')
+ speed = 10
+ if rate.endswith('%'):
+ percent = rate.rstrip('%')
+ speed = int(speed) * int(percent) // 100
+ return int(speed) *1000000 # convert to MBit/s
+
+ rate_numeric = int(''.join([n for n in rate if n.isdigit()]))
+ rate_scale = ''.join([n for n in rate if not n.isdigit()])
+
+ if int(rate_numeric) <= 0:
+ raise ValueError(f'{rate_numeric} is not a valid bandwidth <= 0')
+
+ if rate_scale:
+ return int(rate_numeric * rates[rate_scale])
+ else:
+ # No suffix implies Kbps just as Cisco IOS
+ return int(rate_numeric * 1000)
+
+ def update(self, config, direction, priority=None):
+ """ method must be called from derived class after it has completed qdisc setup """
+
+ if 'class' in config:
+ for cls, cls_config in config['class'].items():
+ self._build_base_qdisc(cls_config, int(cls))
+
+ if 'match' in cls_config:
+ for match, match_config in cls_config['match'].items():
+ for af in ['ip', 'ipv6']:
+ # every match criteria has it's tc instance
+ filter_cmd = f'tc filter replace dev {self._interface} parent {self._parent:x}:'
+
+ if priority:
+ filter_cmd += f' prio {cls}'
+ elif 'priority' in cls_config:
+ prio = cls_config['priority']
+ filter_cmd += f' prio {prio}'
+
+ filter_cmd += ' protocol all u32'
+
+ tc_af = af
+ if af == 'ipv6':
+ tc_af = 'ip6'
+
+ if af in match_config:
+ tmp = dict_search(f'{af}.source.address', match_config)
+ if tmp: filter_cmd += f' match {tc_af} src {tmp}'
+
+ tmp = dict_search(f'{af}.source.port', match_config)
+ if tmp: filter_cmd += f' match {tc_af} sport {tmp} 0xffff'
+
+ tmp = dict_search(f'{af}.destination.address', match_config)
+ if tmp: filter_cmd += f' match {tc_af} dst {tmp}'
+
+ tmp = dict_search(f'{af}.destination.port', match_config)
+ 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'
+
+ # Will match against total length of an IPv4 packet and
+ # payload length of an IPv6 packet.
+ #
+ # IPv4 : match u16 0x0000 ~MAXLEN at 2
+ # IPv6 : match u16 0x0000 ~MAXLEN at 4
+ tmp = dict_search(f'{af}.max_length', match_config)
+ if tmp:
+ # We need the 16 bit two's complement of the maximum
+ # packet length
+ tmp = hex(0xffff & ~int(tmp))
+
+ if af == 'ip':
+ filter_cmd += f' match u16 0x0000 {tmp} at 2'
+ elif af == 'ipv6':
+ filter_cmd += f' match u16 0x0000 {tmp} at 4'
+
+ # We match against specific TCP flags - we assume the IPv4
+ # header length is 20 bytes and assume the IPv6 packet is
+ # not using extension headers (hence a ip header length of 40 bytes)
+ # TCP Flags are set on byte 13 of the TCP header.
+ # IPv4 : match u8 X X at 33
+ # IPv6 : match u8 X X at 53
+ # with X = 0x02 for SYN and X = 0x10 for ACK
+ tmp = dict_search(f'{af}.tcp', match_config)
+ if tmp:
+ mask = 0
+ if 'ack' in tmp:
+ mask |= 0x10
+ if 'syn' in tmp:
+ mask |= 0x02
+ mask = hex(mask)
+
+ if af == 'ip':
+ filter_cmd += f' match u8 {mask} {mask} at 33'
+ elif af == 'ipv6':
+ filter_cmd += f' match u8 {mask} {mask} at 53'
+
+ # 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}'
+
+ 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)
+
diff --git a/python/vyos/qos/cake.py b/python/vyos/qos/cake.py
new file mode 100644
index 000000000..a89b1de1e
--- /dev/null
+++ b/python/vyos/qos/cake.py
@@ -0,0 +1,55 @@
+# Copyright 2022 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.qos.base import QoSBase
+
+class CAKE(QoSBase):
+ _direction = ['egress']
+
+ # https://man7.org/linux/man-pages/man8/tc-cake.8.html
+ def update(self, config, direction):
+ tmp = f'tc qdisc add dev {self._interface} root handle 1: cake {direction}'
+ if 'bandwidth' in config:
+ bandwidth = self._rate_convert(config['bandwidth'])
+ tmp += f' bandwidth {bandwidth}'
+
+ if 'rtt' in config:
+ rtt = config['rtt']
+ tmp += f' rtt {rtt}ms'
+
+ if 'flow_isolation' in config:
+ if 'blind' in config['flow_isolation']:
+ tmp += f' flowblind'
+ if 'dst_host' in config['flow_isolation']:
+ tmp += f' dsthost'
+ if 'dual_dst_host' in config['flow_isolation']:
+ tmp += f' dual-dsthost'
+ if 'dual_src_host' in config['flow_isolation']:
+ tmp += f' dual-srchost'
+ if 'flow' in config['flow_isolation']:
+ tmp += f' flows'
+ if 'host' in config['flow_isolation']:
+ tmp += f' hosts'
+ if 'nat' in config['flow_isolation']:
+ tmp += f' nat'
+ if 'src_host' in config['flow_isolation']:
+ tmp += f' srchost '
+ else:
+ tmp += f' nonat'
+
+ self._cmd(tmp)
+
+ # call base class
+ super().update(config, direction)
diff --git a/python/vyos/qos/droptail.py b/python/vyos/qos/droptail.py
new file mode 100644
index 000000000..427d43d19
--- /dev/null
+++ b/python/vyos/qos/droptail.py
@@ -0,0 +1,28 @@
+# Copyright 2022 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.qos.base import QoSBase
+
+class DropTail(QoSBase):
+ # https://man7.org/linux/man-pages/man8/tc-pfifo.8.html
+ def update(self, config, direction):
+ tmp = f'tc qdisc add dev {self._interface} root pfifo'
+ if 'queue_limit' in config:
+ limit = config["queue_limit"]
+ tmp += f' limit {limit}'
+ self._cmd(tmp)
+
+ # call base class
+ super().update(config, direction)
diff --git a/python/vyos/qos/fairqueue.py b/python/vyos/qos/fairqueue.py
new file mode 100644
index 000000000..f41d098fb
--- /dev/null
+++ b/python/vyos/qos/fairqueue.py
@@ -0,0 +1,31 @@
+# Copyright 2022 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.qos.base import QoSBase
+
+class FairQueue(QoSBase):
+ # https://man7.org/linux/man-pages/man8/tc-sfq.8.html
+ def update(self, config, direction):
+ tmp = f'tc qdisc add dev {self._interface} root sfq'
+
+ if 'hash_interval' in config:
+ tmp += f' perturb {config["hash_interval"]}'
+ if 'queue_limit' in config:
+ tmp += f' limit {config["queue_limit"]}'
+
+ self._cmd(tmp)
+
+ # call base class
+ super().update(config, direction)
diff --git a/python/vyos/qos/fqcodel.py b/python/vyos/qos/fqcodel.py
new file mode 100644
index 000000000..cd2340aa2
--- /dev/null
+++ b/python/vyos/qos/fqcodel.py
@@ -0,0 +1,40 @@
+# Copyright 2022 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.qos.base import QoSBase
+
+class FQCodel(QoSBase):
+ # https://man7.org/linux/man-pages/man8/tc-fq_codel.8.html
+ def update(self, config, direction):
+ tmp = f'tc qdisc add dev {self._interface} root fq_codel'
+
+ if 'codel_quantum' in config:
+ tmp += f' quantum {config["codel_quantum"]}'
+ if 'flows' in config:
+ tmp += f' flows {config["flows"]}'
+ if 'interval' in config:
+ interval = int(config['interval']) * 1000
+ tmp += f' interval {interval}'
+ if 'queue_limit' in config:
+ tmp += f' limit {config["queue_limit"]}'
+ if 'target' in config:
+ target = int(config['target']) * 1000
+ tmp += f' target {target}'
+
+ tmp += f' noecn'
+ self._cmd(tmp)
+
+ # call base class
+ super().update(config, direction)
diff --git a/python/vyos/qos/limiter.py b/python/vyos/qos/limiter.py
new file mode 100644
index 000000000..ace0c0b6c
--- /dev/null
+++ b/python/vyos/qos/limiter.py
@@ -0,0 +1,27 @@
+# Copyright 2022 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.qos.base import QoSBase
+
+class Limiter(QoSBase):
+ _direction = ['ingress']
+
+ def update(self, config, direction):
+ tmp = f'tc qdisc add dev {self._interface} handle {self._parent:x}: {direction}'
+ self._cmd(tmp)
+
+ # base class must be called last
+ super().update(config, direction)
+
diff --git a/python/vyos/qos/netem.py b/python/vyos/qos/netem.py
new file mode 100644
index 000000000..8bdef300b
--- /dev/null
+++ b/python/vyos/qos/netem.py
@@ -0,0 +1,53 @@
+# Copyright 2022 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.qos.base import QoSBase
+
+class NetEm(QoSBase):
+ # https://man7.org/linux/man-pages/man8/tc-netem.8.html
+ def update(self, config, direction):
+ tmp = f'tc qdisc add dev {self._interface} root netem'
+ if 'bandwidth' in config:
+ rate = self._rate_convert(config["bandwidth"])
+ tmp += f' rate {rate}'
+
+ if 'queue_limit' in config:
+ limit = config["queue_limit"]
+ tmp += f' limit {limit}'
+
+ if 'delay' in config:
+ delay = config["delay"]
+ tmp += f' delay {delay}ms'
+
+ if 'loss' in config:
+ drop = config["loss"]
+ tmp += f' drop {drop}%'
+
+ if 'corruption' in config:
+ corrupt = config["corruption"]
+ tmp += f' corrupt {corrupt}%'
+
+ if 'reordering' in config:
+ reorder = config["reordering"]
+ tmp += f' reorder {reorder}%'
+
+ if 'duplicate' in config:
+ duplicate = config["duplicate"]
+ tmp += f' duplicate {duplicate}%'
+
+ self._cmd(tmp)
+
+ # call base class
+ super().update(config, direction)
diff --git a/python/vyos/qos/priority.py b/python/vyos/qos/priority.py
new file mode 100644
index 000000000..6d4a60a43
--- /dev/null
+++ b/python/vyos/qos/priority.py
@@ -0,0 +1,41 @@
+# Copyright 2022 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.qos.base import QoSBase
+from vyos.util import dict_search
+
+class Priority(QoSBase):
+ _parent = 1
+
+ # https://man7.org/linux/man-pages/man8/tc-prio.8.html
+ def update(self, config, direction):
+ if 'class' in config:
+ class_id_max = self._get_class_max_id(config)
+ bands = int(class_id_max) +1
+
+ tmp = f'tc qdisc add dev {self._interface} root handle {self._parent:x}: prio bands {bands} priomap ' \
+ f'{class_id_max} {class_id_max} {class_id_max} {class_id_max} ' \
+ f'{class_id_max} {class_id_max} {class_id_max} {class_id_max} ' \
+ f'{class_id_max} {class_id_max} {class_id_max} {class_id_max} ' \
+ f'{class_id_max} {class_id_max} {class_id_max} {class_id_max} '
+ self._cmd(tmp)
+
+ for cls in config['class']:
+ cls = int(cls)
+ tmp = f'tc qdisc add dev {self._interface} parent {self._parent:x}:{cls:x} pfifo'
+ self._cmd(tmp)
+
+ # base class must be called last
+ super().update(config, direction, priority=True)
diff --git a/python/vyos/qos/randomdetect.py b/python/vyos/qos/randomdetect.py
new file mode 100644
index 000000000..d7d84260f
--- /dev/null
+++ b/python/vyos/qos/randomdetect.py
@@ -0,0 +1,54 @@
+# Copyright 2022 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.qos.base import QoSBase
+
+class RandomDetect(QoSBase):
+ _parent = 1
+
+ # 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'
+ 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}'
+
+ self._cmd(tmp)
+
+ # call base class
+ super().update(config, direction)
diff --git a/python/vyos/qos/ratelimiter.py b/python/vyos/qos/ratelimiter.py
new file mode 100644
index 000000000..a4f80a1be
--- /dev/null
+++ b/python/vyos/qos/ratelimiter.py
@@ -0,0 +1,37 @@
+# Copyright 2022 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.qos.base import QoSBase
+
+class RateLimiter(QoSBase):
+ # https://man7.org/linux/man-pages/man8/tc-tbf.8.html
+ def update(self, config, direction):
+ # call base class
+ super().update(config, direction)
+
+ tmp = f'tc qdisc add dev {self._interface} root tbf'
+ if 'bandwidth' in config:
+ rate = self._rate_convert(config['bandwidth'])
+ tmp += f' rate {rate}'
+
+ if 'burst' in config:
+ burst = config['burst']
+ tmp += f' burst {burst}'
+
+ if 'latency' in config:
+ latency = config['latency']
+ tmp += f' latency {latency}ms'
+
+ self._cmd(tmp)
diff --git a/python/vyos/qos/roundrobin.py b/python/vyos/qos/roundrobin.py
new file mode 100644
index 000000000..80814ddfb
--- /dev/null
+++ b/python/vyos/qos/roundrobin.py
@@ -0,0 +1,44 @@
+# Copyright 2022 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.qos.base import QoSBase
+
+class RoundRobin(QoSBase):
+ _parent = 1
+
+ # https://man7.org/linux/man-pages/man8/tc-drr.8.html
+ def update(self, config, direction):
+ tmp = f'tc qdisc add dev {self._interface} root handle 1: drr'
+ self._cmd(tmp)
+
+ if 'class' in config:
+ for cls in config['class']:
+ cls = int(cls)
+ tmp = f'tc class replace dev {self._interface} parent 1:1 classid 1:{cls:x} drr'
+ self._cmd(tmp)
+
+ tmp = f'tc qdisc replace dev {self._interface} parent 1:{cls:x} pfifo'
+ self._cmd(tmp)
+
+ if 'default' in config:
+ class_id_max = self._get_class_max_id(config)
+ default_cls_id = int(class_id_max) +1
+
+ # class ID via CLI is in range 1-4095, thus 1000 hex = 4096
+ tmp = f'tc class replace dev {self._interface} parent 1:1 classid 1:{default_cls_id:x} drr'
+ self._cmd(tmp)
+
+ # call base class
+ super().update(config, direction, priority=True)
diff --git a/python/vyos/qos/trafficshaper.py b/python/vyos/qos/trafficshaper.py
new file mode 100644
index 000000000..f42f4d022
--- /dev/null
+++ b/python/vyos/qos/trafficshaper.py
@@ -0,0 +1,106 @@
+# Copyright 2022 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 math import ceil
+from vyos.qos.base import QoSBase
+
+# Kernel limits on quantum (bytes)
+MAXQUANTUM = 200000
+MINQUANTUM = 1000
+
+class TrafficShaper(QoSBase):
+ _parent = 1
+
+ # https://man7.org/linux/man-pages/man8/tc-htb.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}: htb r2q {r2q} 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 htb 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)
+
+ # bandwidth is a mandatory CLI node
+ rate = self._rate_convert(cls_config['bandwidth'])
+ burst = cls_config['burst']
+ quantum = cls_config['codel_quantum']
+
+ tmp = f'tc class replace dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{cls:x} htb rate {rate} burst {burst} quantum {quantum}'
+ if 'priority' in cls_config:
+ priority = cls_config['priority']
+ tmp += f' prio {priority}'
+ self._cmd(tmp)
+
+ tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{cls:x} sfq'
+ self._cmd(tmp)
+
+ if 'default' in config:
+ 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}'
+ if 'priority' in config['default']:
+ priority = config['default']['priority']
+ tmp += f' prio {priority}'
+ self._cmd(tmp)
+
+ tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{default_minor_id:x} sfq'
+ self._cmd(tmp)
+
+ # call base class
+ super().update(config, direction)
+
+class TrafficShaperHFSC(TrafficShaper):
+ def update(self, config, direction):
+ # call base class
+ super().update(config, direction)
+
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 6a828c0ac..66ded464d 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -348,9 +348,11 @@ def colon_separated_to_dict(data_string, uniquekeys=False):
l = l.strip()
if l:
match = re.match(key_value_re, l)
- if match:
+ if match and (len(match.groups()) == 2):
key = match.groups()[0].strip()
value = match.groups()[1].strip()
+ else:
+ raise ValueError(f"""Line "{l}" could not be parsed a colon-separated pair """, l)
if key in data.keys():
if uniquekeys:
raise ValueError("Data string has duplicate keys: {0}".format(key))
@@ -486,7 +488,7 @@ def is_listen_port_bind_service(port: int, service: str) -> bool:
Example:
% is_listen_port_bind_service(443, 'nginx')
True
- % is_listen_port_bind_service(443, 'ocservr-main')
+ % is_listen_port_bind_service(443, 'ocserv-main')
False
"""
from psutil import net_connections as connections