summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/system/ssh_config.j23
-rw-r--r--interface-definitions/include/qos/bandwidth-auto.xml.i6
-rw-r--r--interface-definitions/include/qos/bandwidth.xml.i6
-rw-r--r--interface-definitions/protocols-static.xml.in14
-rw-r--r--interface-definitions/qos.xml.in6
-rw-r--r--interface-definitions/system-option.xml.in1
-rw-r--r--python/vyos/qos/base.py46
-rw-r--r--python/vyos/qos/priority.py1
-rw-r--r--python/vyos/qos/roundrobin.py6
-rw-r--r--python/vyos/qos/trafficshaper.py16
-rwxr-xr-xsmoketest/scripts/cli/test_qos.py10
-rwxr-xr-xsrc/conf_mode/protocols_static.py9
-rwxr-xr-xsrc/conf_mode/qos.py3
-rwxr-xr-xsrc/conf_mode/system-option.py17
-rwxr-xr-xsrc/migration-scripts/qos/1-to-242
15 files changed, 106 insertions, 80 deletions
diff --git a/data/templates/system/ssh_config.j2 b/data/templates/system/ssh_config.j2
index 1449f95b1..d3ede0971 100644
--- a/data/templates/system/ssh_config.j2
+++ b/data/templates/system/ssh_config.j2
@@ -1,3 +1,6 @@
{% if ssh_client.source_address is vyos_defined %}
BindAddress {{ ssh_client.source_address }}
{% endif %}
+{% if ssh_client.source_interface is vyos_defined %}
+BindInterface {{ ssh_client.source_interface }}
+{% endif %}
diff --git a/interface-definitions/include/qos/bandwidth-auto.xml.i b/interface-definitions/include/qos/bandwidth-auto.xml.i
index 260bd4f7b..a86f28296 100644
--- a/interface-definitions/include/qos/bandwidth-auto.xml.i
+++ b/interface-definitions/include/qos/bandwidth-auto.xml.i
@@ -33,9 +33,13 @@
<format>&lt;number&gt;tbit</format>
<description>Terabits per second</description>
</valueHelp>
+ <valueHelp>
+ <format>&lt;number&gt;%%</format>
+ <description>Percentage of interface link speed</description>
+ </valueHelp>
<constraint>
<validator name="numeric" argument="--positive"/>
- <regex>(auto|\d+(bit|kbit|mbit|gbit|tbit))</regex>
+ <regex>(auto|\d+(bit|kbit|mbit|gbit|tbit)|(100|\d(\d)?)%)</regex>
</constraint>
</properties>
<defaultValue>auto</defaultValue>
diff --git a/interface-definitions/include/qos/bandwidth.xml.i b/interface-definitions/include/qos/bandwidth.xml.i
index 62ea93b67..f2848f066 100644
--- a/interface-definitions/include/qos/bandwidth.xml.i
+++ b/interface-definitions/include/qos/bandwidth.xml.i
@@ -26,9 +26,13 @@
<format>&lt;number&gt;tbit</format>
<description>Terabits per second</description>
</valueHelp>
+ <valueHelp>
+ <format>&lt;number&gt;%</format>
+ <description>Percentage of interface link speed</description>
+ </valueHelp>
<constraint>
<validator name="numeric" argument="--positive"/>
- <regex>\d+(bit|kbit|mbit|gbit|tbit)</regex>
+ <regex>(\d+(bit|kbit|mbit|gbit|tbit)|(100|\d(\d)?)%)</regex>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/protocols-static.xml.in b/interface-definitions/protocols-static.xml.in
index e89433022..033c7759e 100644
--- a/interface-definitions/protocols-static.xml.in
+++ b/interface-definitions/protocols-static.xml.in
@@ -26,6 +26,20 @@
</constraint>
</properties>
<children>
+ <leafNode name="description">
+ <properties>
+ <help>Table description</help>
+ <constraint>
+ <!--
+ iproute2 only considers the first "word" until whitespace in the name field
+ but does not complain about special characters.
+ We put an artificial limit here to make table descriptions potentially valid node names
+ to avoid quoting and simplify future syntax changes if we decide to make any.
+ -->
+ <regex>[a-zA-Z0-9_\-]+</regex>
+ </constraint>
+ </properties>
+ </leafNode>
#include <include/static/static-route.xml.i>
#include <include/static/static-route6.xml.i>
</children>
diff --git a/interface-definitions/qos.xml.in b/interface-definitions/qos.xml.in
index 36190949e..8809369ff 100644
--- a/interface-definitions/qos.xml.in
+++ b/interface-definitions/qos.xml.in
@@ -201,13 +201,13 @@
<properties>
<help>Upper limit of the SFQ</help>
<valueHelp>
- <format>u32:2-127</format>
+ <format>u32:1-127</format>
<description>Queue size in packets</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 2-127"/>
+ <validator name="numeric" argument="--range 1-127"/>
</constraint>
- <constraintErrorMessage>Queue limit must greater than 1 and less than 128</constraintErrorMessage>
+ <constraintErrorMessage>Queue limit must be in range 1 to 127</constraintErrorMessage>
</properties>
<defaultValue>127</defaultValue>
</leafNode>
diff --git a/interface-definitions/system-option.xml.in b/interface-definitions/system-option.xml.in
index a9fed81fe..bb15e467e 100644
--- a/interface-definitions/system-option.xml.in
+++ b/interface-definitions/system-option.xml.in
@@ -121,6 +121,7 @@
</properties>
<children>
#include <include/source-address-ipv4-ipv6.xml.i>
+ #include <include/source-interface.xml.i>
</children>
</node>
<leafNode name="startup-beep">
diff --git a/python/vyos/qos/base.py b/python/vyos/qos/base.py
index d039bbb0f..28635b5e7 100644
--- a/python/vyos/qos/base.py
+++ b/python/vyos/qos/base.py
@@ -13,17 +13,21 @@
# 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 = True
+ _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):
@@ -41,7 +45,7 @@ class QoSBase:
return tmp[-1]
return None
- def _tmp_qdisc(self, config : dict, cls_id : int):
+ 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.
@@ -116,11 +120,14 @@ class QoSBase:
'tbit' : 1000000000000,
}
- if rate == 'auto':
+ 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()]))
@@ -140,8 +147,7 @@ class QoSBase:
if 'class' in config:
for cls, cls_config in config['class'].items():
-
- self._tmp_qdisc(cls_config, int(cls))
+ self._build_base_qdisc(cls_config, int(cls))
if 'match' in cls_config:
for match, match_config in cls_config['match'].items():
@@ -240,11 +246,10 @@ class QoSBase:
self._cmd(filter_cmd)
if 'default' in config:
- class_id_max = self._get_class_max_id(config)
- default_cls_id = int(class_id_max) +1
-
- if 'default' in config:
- self._tmp_qdisc(config['default'], default_cls_id)
+ 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'
@@ -252,25 +257,26 @@ 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):
+ if any(tmp in ['exceed', 'bandwidth', 'burst'] for tmp in config['default']):
filter_cmd += f' action police'
- if 'exceed' in cls_config:
- action = cls_config['exceed']
+ if 'exceed' in config['default']:
+ action = config['default']['exceed']
filter_cmd += f' conform-exceed {action}'
- if 'not_exceed' in cls_config:
- action = cls_config['not_exceed']
+ if 'not_exceed' in config['default']:
+ action = config['default']['not_exceed']
filter_cmd += f'/{action}'
- if 'bandwidth' in cls_config:
- rate = self._rate_convert(cls_config['bandwidth'])
+ if 'bandwidth' in config['default']:
+ rate = self._rate_convert(config['default']['bandwidth'])
filter_cmd += f' rate {rate}'
- if 'burst' in cls_config:
- burst = cls_config['burst']
+ 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}'
- filter_cmd += f' flowid {self._parent:x}:{default_cls_id:x}'
self._cmd(filter_cmd)
diff --git a/python/vyos/qos/priority.py b/python/vyos/qos/priority.py
index 72092b7ef..6d4a60a43 100644
--- a/python/vyos/qos/priority.py
+++ b/python/vyos/qos/priority.py
@@ -33,6 +33,7 @@ class Priority(QoSBase):
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)
diff --git a/python/vyos/qos/roundrobin.py b/python/vyos/qos/roundrobin.py
index 4a0cb18aa..80814ddfb 100644
--- a/python/vyos/qos/roundrobin.py
+++ b/python/vyos/qos/roundrobin.py
@@ -26,10 +26,10 @@ class RoundRobin(QoSBase):
if 'class' in config:
for cls in config['class']:
cls = int(cls)
- tmp = f'tc class add dev {self._interface} parent 1:1 classid 1:{cls:x} drr'
+ tmp = f'tc class replace dev {self._interface} parent 1:1 classid 1:{cls:x} drr'
self._cmd(tmp)
- tmp = f'tc qdisc add dev {self._interface} parent 1:{cls:x} pfifo'
+ tmp = f'tc qdisc replace dev {self._interface} parent 1:{cls:x} pfifo'
self._cmd(tmp)
if 'default' in config:
@@ -37,7 +37,7 @@ class RoundRobin(QoSBase):
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 add dev {self._interface} parent 1:1 classid 1:{default_cls_id:x} drr'
+ tmp = f'tc class replace dev {self._interface} parent 1:1 classid 1:{default_cls_id:x} drr'
self._cmd(tmp)
# call base class
diff --git a/python/vyos/qos/trafficshaper.py b/python/vyos/qos/trafficshaper.py
index 6d465b38e..f42f4d022 100644
--- a/python/vyos/qos/trafficshaper.py
+++ b/python/vyos/qos/trafficshaper.py
@@ -39,7 +39,6 @@ class TrafficShaper(QoSBase):
# need a bigger r2q if going fast than 16 mbits/sec
if (speed_bps // r2q) >= MAXQUANTUM: # integer division
r2q = ceil(speed_bps // MAXQUANTUM)
- print(r2q)
else:
# if there is a slow class then may need smaller value
if 'class' in config:
@@ -59,10 +58,10 @@ class TrafficShaper(QoSBase):
default_minor_id = int(class_id_max) +1
- tmp = f'tc qdisc add dev {self._interface} root handle {self._parent:x}: htb r2q {r2q} default {default_minor_id:x}' # default is in hex
+ 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 add dev {self._interface} parent {self._parent:x}: classid {self._parent:x}:1 htb rate {speed}'
+ 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:
@@ -75,23 +74,26 @@ class TrafficShaper(QoSBase):
burst = cls_config['burst']
quantum = cls_config['codel_quantum']
- tmp = f'tc class add dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{cls:x} htb rate {rate} burst {burst} quantum {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 add dev {self._interface} parent {self._parent:x}:{cls:x} sfq'
+ tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{cls:x} sfq'
self._cmd(tmp)
if 'default' in config:
- tmp = f'tc class add dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{default_minor_id:x} htb rate {rate} burst {burst} quantum {quantum}'
+ 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 add dev {self._interface} parent {self._parent:x}:{default_minor_id:x} sfq'
+ tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{default_minor_id:x} sfq'
self._cmd(tmp)
# call base class
diff --git a/smoketest/scripts/cli/test_qos.py b/smoketest/scripts/cli/test_qos.py
index d1fa3d07b..014870ec4 100755
--- a/smoketest/scripts/cli/test_qos.py
+++ b/smoketest/scripts/cli/test_qos.py
@@ -130,7 +130,7 @@ class TestQoS(VyOSUnitTestSHIM.TestCase):
def test_03_fair_queue(self):
hash_interval = 10
- queue_limit = 50
+ queue_limit = 5
policy_type = 'fair-queue'
first = True
@@ -150,13 +150,13 @@ class TestQoS(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['policy', policy_type, policy_name, 'queue-limit', str(queue_limit)])
hash_interval += 1
- queue_limit += 10
+ queue_limit += 1
# commit changes
self.cli_commit()
hash_interval = 10
- queue_limit = 50
+ queue_limit = 5
for interface in self._interfaces:
tmp = get_tc_qdisc_json(interface)
@@ -165,7 +165,7 @@ class TestQoS(VyOSUnitTestSHIM.TestCase):
self.assertEqual(queue_limit, tmp['options']['limit'])
hash_interval += 1
- queue_limit += 10
+ queue_limit += 1
def test_04_fq_codel(self):
policy_type = 'fq-codel'
@@ -282,8 +282,6 @@ class TestQoS(VyOSUnitTestSHIM.TestCase):
# commit changes
self.cli_commit()
- self.skipTest('iproute2 bug - invalid JSON')
-
for interface in self._interfaces:
for filter in get_tc_filter_json(interface, 'ingress'):
# bail out early if filter has no attached action
diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py
index 58e202928..cbbc476a7 100755
--- a/src/conf_mode/protocols_static.py
+++ b/src/conf_mode/protocols_static.py
@@ -98,6 +98,15 @@ def generate(static):
return None
def apply(static):
+ ## Put routing table names in /etc/iproute2/rt_tables
+ with open("/etc/iproute2/rt_tables.d/vyos.conf", 'w') as f:
+ print("# Generated by VyOS (protocols_static.py), do not edit by hand", file=f)
+ for t in static['table']:
+ if 'description' in static['table'][t]:
+ print(f"{t}\t{static['table'][t]['description']}", file=f)
+
+ ## Inject routes into FRR
+
static_daemon = 'staticd'
zebra_daemon = 'zebra'
diff --git a/src/conf_mode/qos.py b/src/conf_mode/qos.py
index 2eb03237c..1fe3b6aa9 100755
--- a/src/conf_mode/qos.py
+++ b/src/conf_mode/qos.py
@@ -187,6 +187,9 @@ def verify(qos):
if queue_lim < max_tr:
raise ConfigError(f'Policy "{policy}" uses queue-limit "{queue_lim}" < max-threshold "{max_tr}"!')
+ if 'default' in policy_config:
+ if 'bandwidth' not in policy_config['default']:
+ raise ConfigError('Bandwidth not defined for default traffic!')
# we should check interface ingress/egress configuration after verifying that
# the policy name is used only once - this makes the logic easier!
diff --git a/src/conf_mode/system-option.py b/src/conf_mode/system-option.py
index 36dbf155b..e6c7a0ed2 100755
--- a/src/conf_mode/system-option.py
+++ b/src/conf_mode/system-option.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2020 VyOS maintainers and contributors
+# Copyright (C) 2019-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -22,17 +22,19 @@ from time import sleep
from vyos.config import Config
from vyos.configdict import dict_merge
+from vyos.configverify import verify_source_interface
from vyos.template import render
from vyos.util import cmd
from vyos.util import is_systemd_service_running
from vyos.validate import is_addr_assigned
+from vyos.validate import is_intf_addr_assigned
from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
curlrc_config = r'/etc/curlrc'
-ssh_config = r'/etc/ssh/ssh_config'
+ssh_config = r'/etc/ssh/ssh_config.d/91-vyos-ssh-client-options.conf'
systemd_action_file = '/lib/systemd/system/ctrl-alt-del.target'
def get_config(config=None):
@@ -68,8 +70,17 @@ def verify(options):
if 'ssh_client' in options:
config = options['ssh_client']
if 'source_address' in config:
+ address = config['source_address']
if not is_addr_assigned(config['source_address']):
- raise ConfigError('No interface with give address specified!')
+ raise ConfigError('No interface with address "{address}" configured!')
+
+ if 'source_interface' in config:
+ verify_source_interface(config)
+ if 'source_address' in config:
+ address = config['source_address']
+ interface = config['source_interface']
+ if not is_intf_addr_assigned(interface, address):
+ raise ConfigError(f'Address "{address}" not assigned on interface "{interface}"!')
return None
diff --git a/src/migration-scripts/qos/1-to-2 b/src/migration-scripts/qos/1-to-2
index 6f4c08a50..41026cbd6 100755
--- a/src/migration-scripts/qos/1-to-2
+++ b/src/migration-scripts/qos/1-to-2
@@ -98,49 +98,19 @@ config.set(['qos'])
config.copy(base, ['qos', 'policy'])
config.delete(base)
-# TODO
-# - remove burst from network emulator
-
-def change_cli_bandwidth(config, path):
- if config.exists(path + ['bandwidth']):
- bw = config.return_value(path + ['bandwidth'])
- if bw.endswith('%'):
- bw = bandwidth_percent_to_val(interface, bw.rstrip('%'))
- config.set(path + ['bandwidth'], value=bw)
- return
-
# Now map the interface policy binding to the new CLI syntax
+if len(iface_config):
+ config.set(['qos', 'interface'])
+ config.set_tag(['qos', 'interface'])
+
for interface, interface_config in iface_config.items():
+ config.set(['qos', 'interface', interface])
+ config.set_tag(['qos', 'interface', interface])
if 'ingress' in interface_config:
config.set(['qos', 'interface', interface, 'ingress'], value=interface_config['ingress'])
if 'egress' in interface_config:
config.set(['qos', 'interface', interface, 'egress'], value=interface_config['egress'])
- # QoS policy <-> interface binding is now established - we now can adjust some
- # CLI values like bandwidth in percent
- for direction in ['ingress', 'egress']:
- if direction not in interface_config:
- continue
- # Convert % bandwidth values to absolute values
- for policy in config.list_nodes(['qos', 'policy']):
- for policy_name in config.list_nodes(['qos', 'policy', policy]):
- if policy_name == interface_config[direction]:
- policy_base = ['qos', 'policy', policy, policy_name]
- # This is for the toplevel bandwidth node on a policy
- change_cli_bandwidth(config, policy_base)
-
- # This is for class based bandwidth value
- if config.exists(policy_base + ['class']):
- for cls in config.list_nodes(policy_base + ['class']):
- cls_base = policy_base + ['class', cls]
- change_cli_bandwidth(config, cls_base)
-
- # This is for the bandwidth value specified under the
- # policy "default" tree
- if config.exists(policy_base + ['default']):
- default_base = policy_base + ['default']
- change_cli_bandwidth(config, default_base)
-
# Remove "burst" CLI node from network emulator
netem_base = ['qos', 'policy', 'network-emulator']
if config.exists(netem_base):