diff --git a/interface-definitions/include/qos/mtu.xml.i b/interface-definitions/include/qos/mtu.xml.i new file mode 100644 index 000000000..161d4c27f --- /dev/null +++ b/interface-definitions/include/qos/mtu.xml.i @@ -0,0 +1,14 @@ +<!-- include start from qos/mtu.xml.i --> +<leafNode name="mtu"> + <properties> + <help>MTU size for this class</help> + <valueHelp> + <format>u32:256-65535</format> + <description>Bytes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 256-65535"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/qos.xml.in b/interface-definitions/qos.xml.in index 31b9a7d21..7618c3027 100644 --- a/interface-definitions/qos.xml.in +++ b/interface-definitions/qos.xml.in @@ -1,876 +1,878 @@ <?xml version="1.0"?> <interfaceDefinition> <node name="qos" owner="${vyos_conf_scripts_dir}/qos.py"> <properties> <help>Quality of Service (QoS)</help> <priority>900</priority> </properties> <children> <tagNode name="interface"> <properties> <help>Interface to apply QoS policy</help> <completionHelp> <script>${vyos_completion_dir}/list_interfaces</script> </completionHelp> <valueHelp> <format>txt</format> <description>Interface name</description> </valueHelp> <constraint> #include <include/constraint/interface-name.xml.i> </constraint> </properties> <children> <leafNode name="ingress"> <properties> <help>Interface ingress traffic policy</help> <completionHelp> <path>qos policy limiter</path> </completionHelp> <valueHelp> <format>txt</format> <description>QoS policy to use</description> </valueHelp> <constraint> <regex>[[:alnum:]][-_[:alnum:]]*</regex> </constraint> <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> </properties> </leafNode> <leafNode name="egress"> <properties> <help>Interface egress traffic policy</help> <completionHelp> <path>qos policy cake</path> <path>qos policy drop-tail</path> <path>qos policy fair-queue</path> <path>qos policy fq-codel</path> <path>qos policy network-emulator</path> <path>qos policy priority-queue</path> <path>qos policy random-detect</path> <path>qos policy rate-control</path> <path>qos policy round-robin</path> <path>qos policy shaper</path> <path>qos policy shaper-hfsc</path> </completionHelp> <valueHelp> <format>txt</format> <description>QoS policy to use</description> </valueHelp> <constraint> <regex>[[:alnum:]][-_[:alnum:]]*</regex> </constraint> <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> </properties> </leafNode> </children> </tagNode> <node name="policy"> <properties> <help>Service Policy definitions</help> </properties> <children> <tagNode name="cake"> <properties> <help>Common Applications Kept Enhanced (CAKE)</help> <valueHelp> <format>txt</format> <description>Policy name</description> </valueHelp> <constraint> <regex>[[:alnum:]][-_[:alnum:]]*</regex> </constraint> <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> #include <include/qos/bandwidth.xml.i> <node name="flow-isolation"> <properties> <help>Flow isolation settings</help> </properties> <children> <leafNode name="blind"> <properties> <help>Disables flow isolation, all traffic passes through a single queue</help> <valueless/> </properties> </leafNode> <leafNode name="src-host"> <properties> <help>Flows are defined only by source address</help> <valueless/> </properties> </leafNode> <leafNode name="dst-host"> <properties> <help>Flows are defined only by destination address</help> <valueless/> </properties> </leafNode> <leafNode name="host"> <properties> <help>Flows are defined by source-destination host pairs</help> <valueless/> </properties> </leafNode> <leafNode name="flow"> <properties> <help>Flows are defined by the entire 5-tuple</help> <valueless/> </properties> </leafNode> <leafNode name="dual-src-host"> <properties> <help>Flows are defined by the 5-tuple, fairness is applied first over source addresses, then over individual flows</help> <valueless/> </properties> </leafNode> <leafNode name="dual-dst-host"> <properties> <help>Flows are defined by the 5-tuple, fairness is applied first over destination addresses, then over individual flows</help> <valueless/> </properties> </leafNode> <leafNode name="triple-isolate"> <properties> <help>Flows are defined by the 5-tuple, fairness is applied over source and destination addresses and also over individual flows (default)</help> <valueless/> </properties> </leafNode> <leafNode name="nat"> <properties> <help>Perform NAT lookup before applying flow-isolation rules</help> <valueless/> </properties> </leafNode> </children> </node> <leafNode name="rtt"> <properties> <help>Round-Trip-Time for Active Queue Management (AQM)</help> <valueHelp> <format>u32:1-3600000</format> <description>RTT in ms</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-3600000"/> </constraint> <constraintErrorMessage>RTT must be in range 1 to 3600000 milli-seconds</constraintErrorMessage> </properties> <defaultValue>100</defaultValue> </leafNode> </children> </tagNode> <tagNode name="drop-tail"> <properties> <help>Packet limited First In, First Out queue</help> <valueHelp> <format>txt</format> <description>Policy name</description> </valueHelp> <constraint> <regex>[[:alnum:]][-_[:alnum:]]*</regex> </constraint> <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> #include <include/qos/queue-limit-1-4294967295.xml.i> </children> </tagNode> <tagNode name="fair-queue"> <properties> <help>Stochastic Fairness Queueing</help> <valueHelp> <format>txt</format> <description>Policy name</description> </valueHelp> <constraint> <regex>[[:alnum:]][-_[:alnum:]]*</regex> </constraint> <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> <leafNode name="hash-interval"> <properties> <help>Interval in seconds for queue algorithm perturbation</help> <valueHelp> <format>u32:0</format> <description>No perturbation</description> </valueHelp> <valueHelp> <format>u32:1-127</format> <description>Interval in seconds for queue algorithm perturbation (advised: 10)</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-127"/> </constraint> <constraintErrorMessage>Interval must be in range 0 to 127</constraintErrorMessage> </properties> <defaultValue>0</defaultValue> </leafNode> <leafNode name="queue-limit"> <properties> <help>Upper limit of the SFQ</help> <valueHelp> <format>u32:1-127</format> <description>Queue size in packets</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-127"/> </constraint> <constraintErrorMessage>Queue limit must be in range 1 to 127</constraintErrorMessage> </properties> <defaultValue>127</defaultValue> </leafNode> </children> </tagNode> <tagNode name="fq-codel"> <properties> <help>Fair Queuing (FQ) with Controlled Delay (CoDel)</help> <valueHelp> <format>txt</format> <description>Policy name</description> </valueHelp> <constraint> <regex>[[:alnum:]][-_[:alnum:]]*</regex> </constraint> <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> #include <include/qos/codel-quantum.xml.i> #include <include/qos/flows.xml.i> #include <include/qos/interval.xml.i> #include <include/qos/queue-limit-2-10999.xml.i> #include <include/qos/target.xml.i> </children> </tagNode> <tagNode name="limiter"> <properties> <help>Traffic input limiting policy</help> <valueHelp> <format>txt</format> <description>Policy name</description> </valueHelp> <constraint> <regex>[[:alnum:]][-_[:alnum:]]*</regex> </constraint> <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> <tagNode name="class"> <properties> <help>Class ID</help> <valueHelp> <format>u32:1-4090</format> <description>Class Identifier</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-4090"/> </constraint> <constraintErrorMessage>Class identifier must be between 1 and 4090</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> #include <include/qos/bandwidth.xml.i> #include <include/qos/burst.xml.i> + #include <include/qos/mtu.xml.i> #include <include/qos/class-police-exceed.xml.i> #include <include/qos/class-match.xml.i> #include <include/qos/class-priority.xml.i> <leafNode name="priority"> <defaultValue>20</defaultValue> </leafNode> </children> </tagNode> <node name="default"> <properties> <help>Default policy</help> </properties> <children> #include <include/qos/bandwidth.xml.i> #include <include/qos/burst.xml.i> + #include <include/qos/mtu.xml.i> #include <include/qos/class-police-exceed.xml.i> </children> </node> </children> </tagNode> <tagNode name="network-emulator"> <properties> <help>Network emulator policy</help> <valueHelp> <format>txt</format> <description>Policy name</description> </valueHelp> <constraint> <regex>[[:alnum:]][-_[:alnum:]]*</regex> </constraint> <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> #include <include/qos/bandwidth.xml.i> <leafNode name="delay"> <properties> <help>Adds delay to packets outgoing to chosen network interface</help> <valueHelp> <format><number></format> <description>Time in milliseconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-65535"/> </constraint> <constraintErrorMessage>Priority must be between 0 and 65535</constraintErrorMessage> </properties> </leafNode> <leafNode name="corruption"> <properties> <help>Introducing error in a random position for chosen percent of packets</help> <valueHelp> <format><number></format> <description>Percentage of packets affected</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-100"/> </constraint> <constraintErrorMessage>Priority must be between 0 and 100</constraintErrorMessage> </properties> </leafNode> <leafNode name="duplicate"> <properties> <help>Cosen percent of packets is duplicated before queuing them</help> <valueHelp> <format><number></format> <description>Percentage of packets affected</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-100"/> </constraint> <constraintErrorMessage>Priority must be between 0 and 100</constraintErrorMessage> </properties> </leafNode> <leafNode name="loss"> <properties> <help>Add independent loss probability to the packets outgoing to chosen network interface</help> <valueHelp> <format><number></format> <description>Percentage of packets affected</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-100"/> </constraint> <constraintErrorMessage>Must be between 0 and 100</constraintErrorMessage> </properties> </leafNode> <leafNode name="reordering"> <properties> <help>Emulated packet reordering percentage</help> <valueHelp> <format><number></format> <description>Percentage of packets affected</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-100"/> </constraint> <constraintErrorMessage>Must be between 0 and 100</constraintErrorMessage> </properties> </leafNode> #include <include/qos/queue-limit-1-4294967295.xml.i> </children> </tagNode> <tagNode name="priority-queue"> <properties> <help>Priority queuing based policy</help> <valueHelp> <format>txt</format> <description>Policy name</description> </valueHelp> <constraint> <regex>[[:alnum:]][-_[:alnum:]]*</regex> </constraint> <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> <tagNode name="class"> <properties> <help>Class Handle</help> <valueHelp> <format>u32:1-7</format> <description>Priority</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-7"/> </constraint> <constraintErrorMessage>Class handle must be between 1 and 7</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> #include <include/qos/codel-quantum.xml.i> #include <include/qos/flows.xml.i> #include <include/qos/interval.xml.i> #include <include/qos/class-match.xml.i> #include <include/qos/queue-limit-1-4294967295.xml.i> #include <include/qos/queue-type.xml.i> <leafNode name="queue-type"> <defaultValue>drop-tail</defaultValue> </leafNode> #include <include/qos/target.xml.i> </children> </tagNode> <node name="default"> <properties> <help>Default policy</help> </properties> <children> #include <include/qos/codel-quantum.xml.i> #include <include/qos/flows.xml.i> #include <include/qos/interval.xml.i> #include <include/qos/queue-limit-1-4294967295.xml.i> #include <include/qos/queue-type.xml.i> <leafNode name="queue-type"> <defaultValue>drop-tail</defaultValue> </leafNode> #include <include/qos/target.xml.i> </children> </node> </children> </tagNode> <tagNode name="random-detect"> <properties> <help>Weighted Random Early Detect policy</help> <valueHelp> <format>txt</format> <description>Policy name</description> </valueHelp> <constraint> <regex>[[:alnum:]][-_[:alnum:]]*</regex> </constraint> <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> #include <include/qos/bandwidth-auto.xml.i> <tagNode name="precedence"> <properties> <help>IP precedence</help> <valueHelp> <format>u32:0-7</format> <description>IP precedence value</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-7"/> </constraint> <constraintErrorMessage>IP precedence value must be between 0 and 7</constraintErrorMessage> </properties> <children> #include <include/qos/queue-limit-1-4294967295.xml.i> <leafNode name="average-packet"> <properties> <help>Average packet size (bytes)</help> <valueHelp> <format>u32:16-10240</format> <description>Average packet size in bytes</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-100"/> </constraint> <constraintErrorMessage>Average packet size must be between 16 and 10240</constraintErrorMessage> </properties> <defaultValue>1024</defaultValue> </leafNode> <leafNode name="mark-probability"> <properties> <help>Mark probability for this precedence</help> <valueHelp> <format><number></format> <description>Numeric value (1/N)</description> </valueHelp> <constraint> <validator name="numeric" argument="--positive"/> </constraint> <constraintErrorMessage>Mark probability must be greater than 0</constraintErrorMessage> </properties> <defaultValue>10</defaultValue> </leafNode> <leafNode name="maximum-threshold"> <properties> <help>Maximum threshold for random detection</help> <valueHelp> <format>u32:0-4096</format> <description>Maximum Threshold in packets</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-4096"/> </constraint> <constraintErrorMessage>Threshold must be between 0 and 4096</constraintErrorMessage> </properties> <defaultValue>18</defaultValue> </leafNode> <leafNode name="minimum-threshold"> <properties> <help>Minimum threshold for random detection</help> <valueHelp> <format>u32:0-4096</format> <description>Maximum Threshold in packets</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-4096"/> </constraint> <constraintErrorMessage>Threshold must be between 0 and 4096</constraintErrorMessage> </properties> </leafNode> </children> </tagNode> </children> </tagNode> <tagNode name="rate-control"> <properties> <help>Rate limiting policy (Token Bucket Filter)</help> <valueHelp> <format>txt</format> <description>Policy name</description> </valueHelp> <constraint> <regex>[[:alnum:]][-_[:alnum:]]*</regex> </constraint> <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> #include <include/qos/bandwidth.xml.i> #include <include/qos/burst.xml.i> <leafNode name="latency"> <properties> <help>Maximum latency</help> <valueHelp> <format><number></format> <description>Time in milliseconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-4096"/> </constraint> <constraintErrorMessage>Threshold must be between 0 and 4096</constraintErrorMessage> </properties> <defaultValue>50</defaultValue> </leafNode> </children> </tagNode> <tagNode name="round-robin"> <properties> <help>Deficit Round Robin Scheduler</help> <valueHelp> <format>txt</format> <description>Policy name</description> </valueHelp> <constraint> <regex>[[:alnum:]][-_[:alnum:]]*</regex> </constraint> <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> <tagNode name="class"> <properties> <help>Class ID</help> <valueHelp> <format>u32:1-4095</format> <description>Class Identifier</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-4095"/> </constraint> <constraintErrorMessage>Class identifier must be between 1 and 4095</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> #include <include/qos/codel-quantum.xml.i> #include <include/qos/flows.xml.i> #include <include/qos/interval.xml.i> #include <include/qos/class-match.xml.i> <leafNode name="quantum"> <properties> <help>Packet scheduling quantum</help> <valueHelp> <format>u32:1-4294967295</format> <description>Packet scheduling quantum (bytes)</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-4294967295"/> </constraint> <constraintErrorMessage>Quantum must be in range 1 to 4294967295</constraintErrorMessage> </properties> </leafNode> #include <include/qos/queue-limit-1-4294967295.xml.i> #include <include/qos/queue-type.xml.i> <leafNode name="queue-type"> <defaultValue>drop-tail</defaultValue> </leafNode> #include <include/qos/target.xml.i> </children> </tagNode> <node name="default"> <properties> <help>Default policy</help> </properties> <children> #include <include/qos/codel-quantum.xml.i> #include <include/qos/flows.xml.i> #include <include/qos/interval.xml.i> #include <include/qos/queue-limit-1-4294967295.xml.i> #include <include/qos/queue-type.xml.i> <leafNode name="queue-type"> <defaultValue>fair-queue</defaultValue> </leafNode> #include <include/qos/target.xml.i> </children> </node> </children> </tagNode> <tagNode name="shaper"> <properties> <help>Traffic shaping based policy (Hierarchy Token Bucket)</help> <valueHelp> <format>txt</format> <description>Policy name</description> </valueHelp> <constraint> <regex>[[:alnum:]][-_[:alnum:]]*</regex> </constraint> <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> #include <include/qos/bandwidth-auto.xml.i> <tagNode name="class"> <properties> <help>Class ID</help> <valueHelp> <format>u32:2-4095</format> <description>Class Identifier</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 2-4095"/> </constraint> <constraintErrorMessage>Class identifier must be between 2 and 4095</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> #include <include/qos/bandwidth-auto.xml.i> #include <include/qos/burst.xml.i> <leafNode name="ceiling"> <properties> <help>Bandwidth limit for this class</help> <valueHelp> <format><number></format> <description>Rate in kbit (kilobit per second)</description> </valueHelp> <valueHelp> <format><number>%%</format> <description>Percentage of overall rate</description> </valueHelp> <valueHelp> <format><number>bit</format> <description>bit(1), kbit(10^3), mbit(10^6), gbit, tbit</description> </valueHelp> <valueHelp> <format><number>ibit</format> <description>kibit(1024), mibit(1024^2), gibit(1024^3), tbit(1024^4)</description> </valueHelp> <valueHelp> <format><number>ibps</format> <description>kibps(1024*8), mibps(1024^2*8), gibps, tibps - Byte/sec</description> </valueHelp> <valueHelp> <format><number>bps</format> <description>bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec</description> </valueHelp> </properties> </leafNode> #include <include/qos/codel-quantum.xml.i> #include <include/qos/flows.xml.i> #include <include/qos/interval.xml.i> #include <include/qos/class-match.xml.i> #include <include/qos/class-priority.xml.i> #include <include/qos/queue-limit-1-4294967295.xml.i> #include <include/qos/queue-type.xml.i> <leafNode name="queue-type"> <defaultValue>fq-codel</defaultValue> </leafNode> #include <include/qos/set-dscp.xml.i> #include <include/qos/target.xml.i> </children> </tagNode> <node name="default"> <properties> <help>Default policy</help> </properties> <children> #include <include/qos/bandwidth.xml.i> #include <include/qos/burst.xml.i> <leafNode name="ceiling"> <properties> <help>Bandwidth limit for this class</help> <valueHelp> <format><number></format> <description>Rate in kbit (kilobit per second)</description> </valueHelp> <valueHelp> <format><number>%%</format> <description>Percentage of overall rate</description> </valueHelp> <valueHelp> <format><number>bit</format> <description>bit(1), kbit(10^3), mbit(10^6), gbit, tbit</description> </valueHelp> <valueHelp> <format><number>ibit</format> <description>kibit(1024), mibit(1024^2), gibit(1024^3), tbit(1024^4)</description> </valueHelp> <valueHelp> <format><number>ibps</format> <description>kibps(1024*8), mibps(1024^2*8), gibps, tibps - Byte/sec</description> </valueHelp> <valueHelp> <format><number>bps</format> <description>bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec</description> </valueHelp> </properties> </leafNode> #include <include/qos/codel-quantum.xml.i> #include <include/qos/flows.xml.i> #include <include/qos/interval.xml.i> <leafNode name="priority"> <properties> <help>Priority for usage of excess bandwidth</help> <valueHelp> <format>u32:0-7</format> <description>Priority order for bandwidth pool</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-7"/> </constraint> <constraintErrorMessage>Priority must be between 0 and 7</constraintErrorMessage> </properties> <defaultValue>20</defaultValue> </leafNode> #include <include/qos/queue-limit-1-4294967295.xml.i> #include <include/qos/queue-type.xml.i> <leafNode name="queue-type"> <defaultValue>fq-codel</defaultValue> </leafNode> #include <include/qos/set-dscp.xml.i> #include <include/qos/target.xml.i> </children> </node> </children> </tagNode> <tagNode name="shaper-hfsc"> <properties> <help>Hierarchical Fair Service Curve's policy</help> <valueHelp> <format>txt</format> <description>Policy name</description> </valueHelp> <constraint> <regex>[[:alnum:]][-_[:alnum:]]*</regex> </constraint> <constraintErrorMessage>Only alpha-numeric policy name allowed</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> #include <include/qos/bandwidth-auto.xml.i> <tagNode name="class"> <properties> <help>Class ID</help> <valueHelp> <format>u32:1-4095</format> <description>Class Identifier</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-4095"/> </constraint> <constraintErrorMessage>Class identifier must be between 1 and 4095</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> <node name="linkshare"> <properties> <help>Linkshare class settings</help> </properties> <children> #include <include/qos/hfsc-d.xml.i> #include <include/qos/hfsc-m1.xml.i> #include <include/qos/hfsc-m2.xml.i> </children> </node> #include <include/qos/class-match.xml.i> <node name="realtime"> <properties> <help>Realtime class settings</help> </properties> <children> #include <include/qos/hfsc-d.xml.i> #include <include/qos/hfsc-m1.xml.i> #include <include/qos/hfsc-m2.xml.i> </children> </node> <node name="upperlimit"> <properties> <help>Upperlimit class settings</help> </properties> <children> #include <include/qos/hfsc-d.xml.i> #include <include/qos/hfsc-m1.xml.i> #include <include/qos/hfsc-m2.xml.i> </children> </node> </children> </tagNode> <node name="default"> <properties> <help>Default policy</help> </properties> <children> <node name="linkshare"> <properties> <help>Linkshare class settings</help> </properties> <children> #include <include/qos/hfsc-d.xml.i> #include <include/qos/hfsc-m1.xml.i> #include <include/qos/hfsc-m2.xml.i> </children> </node> <node name="realtime"> <properties> <help>Realtime class settings</help> </properties> <children> #include <include/qos/hfsc-d.xml.i> #include <include/qos/hfsc-m1.xml.i> #include <include/qos/hfsc-m2.xml.i> </children> </node> <node name="upperlimit"> <properties> <help>Upperlimit class settings</help> </properties> <children> #include <include/qos/hfsc-d.xml.i> #include <include/qos/hfsc-m1.xml.i> #include <include/qos/hfsc-m2.xml.i> </children> </node> </children> </node> </children> </tagNode> </children> </node> </children> </node> </interfaceDefinition> diff --git a/python/vyos/qos/base.py b/python/vyos/qos/base.py index 47318122b..c8e881ee2 100644 --- a/python/vyos/qos/base.py +++ b/python/vyos/qos/base.py @@ -1,393 +1,402 @@ # Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # 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 import jmespath from vyos.base import Warning from vyos.utils.process import cmd from vyos.utils.dict import dict_search from vyos.utils.file 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 } qostype = None 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 _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 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' if queue_limit: tmp += f' 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}ms' 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}ms' 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 = 1000 default_speed = speed # Not all interfaces have valid entries in the speed file. PPPoE # interfaces have the appropriate speed file, but you can not read it: # cat: /sys/class/net/pppoe7/speed: Invalid argument try: speed = read_file(f'/sys/class/net/{self._interface}/speed') if not speed.isnumeric(): Warning('Interface speed cannot be determined (assuming 1000 Mbit/s)') if int(speed) < 1: speed = default_speed if rate.endswith('%'): percent = rate.rstrip('%') speed = int(speed) * int(percent) // 100 except: pass 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 self._debug: import pprint pprint.pprint(config) if 'class' in config: for cls, cls_config in config['class'].items(): self._build_base_qdisc(cls_config, int(cls)) # every match criteria has it's tc instance filter_cmd_base = f'tc filter add dev {self._interface} parent {self._parent:x}:' if priority: filter_cmd_base += f' prio {cls}' elif 'priority' in cls_config: prio = cls_config['priority'] filter_cmd_base += f' prio {prio}' filter_cmd_base += ' protocol all' if 'match' in cls_config: for index, (match, match_config) in enumerate(cls_config['match'].items(), start=1): filter_cmd = filter_cmd_base if self.qostype == 'shaper' and 'prio ' not in filter_cmd: filter_cmd += f' prio {index}' if 'mark' in match_config: mark = match_config['mark'] filter_cmd += f' handle {mark} fw' if 'vif' in match_config: vif = match_config['vif'] filter_cmd += f' basic match "meta(vlan mask 0xfff eq {vif})"' for af in ['ip', 'ipv6']: tc_af = af if af == 'ipv6': tc_af = 'ip6' if af in match_config: filter_cmd += ' u32' 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: 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. # # 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' cls = int(cls) filter_cmd += f' flowid {self._parent:x}:{cls:x}' self._cmd(filter_cmd) vlan_expression = "match.*.vif" match_vlan = jmespath.search(vlan_expression, cls_config) if any(tmp in ['exceed', 'bandwidth', 'burst'] for tmp in cls_config): # For "vif" "basic match" is used instead of "action police" T5961 if not match_vlan: 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}' + + if 'mtu' in cls_config: + mtu = cls_config['mtu'] + filter_cmd += f' mtu {mtu}' + cls = int(cls) filter_cmd += f' flowid {self._parent:x}:{cls:x}' self._cmd(filter_cmd) # 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 # 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}' if 'default' in config: default_cls_id = 1 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) if self.qostype == 'limiter': if 'default' in config: 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 'mtu' in config['default']: + mtu = config['default']['mtu'] + filter_cmd += f' mtu {mtu}' + if 'class' in config: filter_cmd += f' flowid {self._parent:x}:{default_cls_id:x}' self._cmd(filter_cmd) diff --git a/smoketest/scripts/cli/test_qos.py b/smoketest/scripts/cli/test_qos.py index 81e7326f8..46ef68b1d 100755 --- a/smoketest/scripts/cli/test_qos.py +++ b/smoketest/scripts/cli/test_qos.py @@ -1,602 +1,641 @@ #!/usr/bin/env python3 # # Copyright (C) 2022-2023 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 # published by the Free Software Foundation. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import os import unittest from json import loads from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section from vyos.utils.process import cmd base_path = ['qos'] def get_tc_qdisc_json(interface) -> dict: tmp = cmd(f'tc -detail -json qdisc show dev {interface}') tmp = loads(tmp) return next(iter(tmp)) def get_tc_filter_json(interface, direction) -> list: if direction not in ['ingress', 'egress']: raise ValueError() tmp = cmd(f'tc -detail -json filter show dev {interface} {direction}') tmp = loads(tmp) return tmp +def get_tc_filter_details(interface, direction) -> list: + # json doesn't contain all params, such as mtu + if direction not in ['ingress', 'egress']: + raise ValueError() + tmp = cmd(f'tc -details filter show dev {interface} {direction}') + return tmp + class TestQoS(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): super(TestQoS, cls).setUpClass() # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) # We only test on physical interfaces and not VLAN (sub-)interfaces cls._interfaces = [] if 'TEST_ETH' in os.environ: tmp = os.environ['TEST_ETH'].split() cls._interfaces = tmp else: for tmp in Section.interfaces('ethernet', vlan=False): cls._interfaces.append(tmp) def tearDown(self): # delete testing SSH config self.cli_delete(base_path) self.cli_commit() def test_01_cake(self): bandwidth = 1000000 rtt = 200 for interface in self._interfaces: policy_name = f'qos-policy-{interface}' self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) self.cli_set(base_path + ['policy', 'cake', policy_name, 'bandwidth', str(bandwidth)]) self.cli_set(base_path + ['policy', 'cake', policy_name, 'rtt', str(rtt)]) self.cli_set(base_path + ['policy', 'cake', policy_name, 'flow-isolation', 'dual-src-host']) bandwidth += 1000000 rtt += 20 # commit changes self.cli_commit() bandwidth = 1000000 rtt = 200 for interface in self._interfaces: tmp = get_tc_qdisc_json(interface) self.assertEqual('cake', tmp['kind']) # TC store rates as a 32-bit unsigned integer in bps (Bytes per second) self.assertEqual(int(bandwidth *125), tmp['options']['bandwidth']) # RTT internally is in us self.assertEqual(int(rtt *1000), tmp['options']['rtt']) self.assertEqual('dual-srchost', tmp['options']['flowmode']) self.assertFalse(tmp['options']['ingress']) self.assertFalse(tmp['options']['nat']) self.assertTrue(tmp['options']['raw']) bandwidth += 1000000 rtt += 20 def test_02_drop_tail(self): queue_limit = 50 first = True for interface in self._interfaces: policy_name = f'qos-policy-{interface}' if first: self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) # verify() - selected QoS policy on interface only supports egress with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) first = False self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) self.cli_set(base_path + ['policy', 'drop-tail', policy_name, 'queue-limit', str(queue_limit)]) queue_limit += 10 # commit changes self.cli_commit() queue_limit = 50 for interface in self._interfaces: tmp = get_tc_qdisc_json(interface) self.assertEqual('pfifo', tmp['kind']) self.assertEqual(queue_limit, tmp['options']['limit']) queue_limit += 10 def test_03_fair_queue(self): hash_interval = 10 queue_limit = 5 policy_type = 'fair-queue' first = True for interface in self._interfaces: policy_name = f'qos-policy-{interface}' if first: self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) # verify() - selected QoS policy on interface only supports egress with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) first = False self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) self.cli_set(base_path + ['policy', policy_type, policy_name, 'hash-interval', str(hash_interval)]) self.cli_set(base_path + ['policy', policy_type, policy_name, 'queue-limit', str(queue_limit)]) hash_interval += 1 queue_limit += 1 # commit changes self.cli_commit() hash_interval = 10 queue_limit = 5 for interface in self._interfaces: tmp = get_tc_qdisc_json(interface) self.assertEqual('sfq', tmp['kind']) self.assertEqual(hash_interval, tmp['options']['perturb']) self.assertEqual(queue_limit, tmp['options']['limit']) hash_interval += 1 queue_limit += 1 def test_04_fq_codel(self): policy_type = 'fq-codel' codel_quantum = 1500 flows = 512 interval = 100 queue_limit = 2048 target = 5 first = True for interface in self._interfaces: policy_name = f'qos-policy-{interface}' if first: self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) # verify() - selected QoS policy on interface only supports egress with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) first = False self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) self.cli_set(base_path + ['policy', policy_type, policy_name, 'codel-quantum', str(codel_quantum)]) self.cli_set(base_path + ['policy', policy_type, policy_name, 'flows', str(flows)]) self.cli_set(base_path + ['policy', policy_type, policy_name, 'interval', str(interval)]) self.cli_set(base_path + ['policy', policy_type, policy_name, 'queue-limit', str(queue_limit)]) self.cli_set(base_path + ['policy', policy_type, policy_name, 'target', str(target)]) codel_quantum += 10 flows += 2 interval += 10 queue_limit += 512 target += 1 # commit changes self.cli_commit() codel_quantum = 1500 flows = 512 interval = 100 queue_limit = 2048 target = 5 for interface in self._interfaces: tmp = get_tc_qdisc_json(interface) self.assertEqual('fq_codel', tmp['kind']) self.assertEqual(codel_quantum, tmp['options']['quantum']) self.assertEqual(flows, tmp['options']['flows']) self.assertEqual(queue_limit, tmp['options']['limit']) # due to internal rounding we need to substract 1 from interval and target after converting to milliseconds # configuration of: # tc qdisc add dev eth0 root fq_codel quantum 1500 flows 512 interval 100ms limit 2048 target 5ms noecn # results in: tc -j qdisc show dev eth0 # [{"kind":"fq_codel","handle":"8046:","root":true,"refcnt":3,"options":{"limit":2048,"flows":512, # "quantum":1500,"target":4999,"interval":99999,"memory_limit":33554432,"drop_batch":64}}] self.assertAlmostEqual(tmp['options']['interval'], interval *1000, delta=1) self.assertAlmostEqual(tmp['options']['target'], target *1000 -1, delta=1) codel_quantum += 10 flows += 2 interval += 10 queue_limit += 512 target += 1 def test_05_limiter(self): qos_config = { '1' : { - 'bandwidth' : '1000000', + 'bandwidth' : '3000000', + 'exceed' : 'pipe', + 'burst' : '100Kb', + 'mtu' : '1600', + 'not-exceed' : 'continue', + 'priority': '15', 'match4' : { 'ssh' : { 'dport' : '22', }, }, }, '2' : { 'bandwidth' : '1000000', 'match6' : { 'ssh' : { 'dport' : '22', }, }, }, } first = True for interface in self._interfaces: policy_name = f'qos-policy-{interface}' if first: self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) # verify() - selected QoS policy on interface only supports egress with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_delete(base_path + ['interface', interface, 'egress', policy_name]) first = False self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) # set default bandwidth parameter for all remaining connections self.cli_set(base_path + ['policy', 'limiter', policy_name, 'default', 'bandwidth', '500000']) + self.cli_set(base_path + ['policy', 'limiter', policy_name, 'default', 'burst', '200kb']) + self.cli_set(base_path + ['policy', 'limiter', policy_name, 'default', 'exceed', 'drop']) + self.cli_set(base_path + ['policy', 'limiter', policy_name, 'default', 'mtu', '3000']) + self.cli_set(base_path + ['policy', 'limiter', policy_name, 'default', 'not-exceed', 'ok']) for qos_class, qos_class_config in qos_config.items(): qos_class_base = base_path + ['policy', 'limiter', policy_name, 'class', qos_class] if 'match4' in qos_class_config: for match, match_config in qos_class_config['match4'].items(): if 'dport' in match_config: self.cli_set(qos_class_base + ['match', match, 'ip', 'destination', 'port', match_config['dport']]) if 'match6' in qos_class_config: for match, match_config in qos_class_config['match6'].items(): if 'dport' in match_config: self.cli_set(qos_class_base + ['match', match, 'ipv6', 'destination', 'port', match_config['dport']]) if 'bandwidth' in qos_class_config: self.cli_set(qos_class_base + ['bandwidth', qos_class_config['bandwidth']]) + if 'exceed' in qos_class_config: + self.cli_set(qos_class_base + ['exceed', qos_class_config['exceed']]) + + if 'not-exceed' in qos_class_config: + self.cli_set(qos_class_base + ['not-exceed', qos_class_config['not-exceed']]) + + if 'burst' in qos_class_config: + self.cli_set(qos_class_base + ['burst', qos_class_config['burst']]) + + if 'mtu' in qos_class_config: + self.cli_set(qos_class_base + ['mtu', qos_class_config['mtu']]) + + if 'priority' in qos_class_config: + self.cli_set(qos_class_base + ['priority', qos_class_config['priority']]) + # commit changes self.cli_commit() for interface in self._interfaces: for filter in get_tc_filter_json(interface, 'ingress'): # bail out early if filter has no attached action if 'options' not in filter or 'actions' not in filter['options']: continue for qos_class, qos_class_config in qos_config.items(): # Every flowid starts with ffff and we encopde the class number after the colon if 'flowid' not in filter['options'] or filter['options']['flowid'] != f'ffff:{qos_class}': continue ip_hdr_offset = 20 if 'match6' in qos_class_config: ip_hdr_offset = 40 self.assertEqual(ip_hdr_offset, filter['options']['match']['off']) if 'dport' in match_config: dport = int(match_config['dport']) self.assertEqual(f'{dport:x}', filter['options']['match']['value']) + tc_details = get_tc_filter_details(interface, 'ingress') + self.assertTrue('filter parent ffff: protocol all pref 20 u32 chain 0' in tc_details) + self.assertTrue('rate 1Gbit burst 15125b mtu 2Kb action drop overhead 0b linklayer ethernet' in tc_details) + self.assertTrue('filter parent ffff: protocol all pref 15 u32 chain 0' in tc_details) + self.assertTrue('rate 3Gbit burst 102000b mtu 1600b action pipe/continue overhead 0b linklayer ethernet' in tc_details) + self.assertTrue('rate 500Mbit burst 204687b mtu 3000b action drop overhead 0b linklayer ethernet' in tc_details) + self.assertTrue('filter parent ffff: protocol all pref 255 basic chain 0' in tc_details) + def test_06_network_emulator(self): policy_type = 'network-emulator' bandwidth = 1000000 corruption = 1 delay = 2 duplicate = 3 loss = 4 queue_limit = 5 reordering = 6 first = True for interface in self._interfaces: policy_name = f'qos-policy-{interface}' if first: self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) # verify() - selected QoS policy on interface only supports egress with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) first = False self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) self.cli_set(base_path + ['policy', policy_type, policy_name, 'bandwidth', str(bandwidth)]) self.cli_set(base_path + ['policy', policy_type, policy_name, 'corruption', str(corruption)]) self.cli_set(base_path + ['policy', policy_type, policy_name, 'delay', str(delay)]) self.cli_set(base_path + ['policy', policy_type, policy_name, 'duplicate', str(duplicate)]) self.cli_set(base_path + ['policy', policy_type, policy_name, 'loss', str(loss)]) self.cli_set(base_path + ['policy', policy_type, policy_name, 'queue-limit', str(queue_limit)]) self.cli_set(base_path + ['policy', policy_type, policy_name, 'reordering', str(reordering)]) bandwidth += 1000000 corruption += 1 delay += 1 duplicate +=1 loss += 1 queue_limit += 1 reordering += 1 # commit changes self.cli_commit() bandwidth = 1000000 corruption = 1 delay = 2 duplicate = 3 loss = 4 queue_limit = 5 reordering = 6 for interface in self._interfaces: tmp = get_tc_qdisc_json(interface) self.assertEqual('netem', tmp['kind']) self.assertEqual(int(bandwidth *125), tmp['options']['rate']['rate']) # values are in % self.assertEqual(corruption/100, tmp['options']['corrupt']['corrupt']) self.assertEqual(duplicate/100, tmp['options']['duplicate']['duplicate']) self.assertEqual(loss/100, tmp['options']['loss-random']['loss']) self.assertEqual(reordering/100, tmp['options']['reorder']['reorder']) self.assertEqual(delay/1000, tmp['options']['delay']['delay']) self.assertEqual(queue_limit, tmp['options']['limit']) bandwidth += 1000000 corruption += 1 delay += 1 duplicate += 1 loss += 1 queue_limit += 1 reordering += 1 def test_07_priority_queue(self): priorities = ['1', '2', '3', '4', '5'] first = True for interface in self._interfaces: policy_name = f'qos-policy-{interface}' if first: self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) # verify() - selected QoS policy on interface only supports egress with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) first = False self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) self.cli_set(base_path + ['policy', 'priority-queue', policy_name, 'default', 'queue-limit', '10']) for priority in priorities: prio_base = base_path + ['policy', 'priority-queue', policy_name, 'class', priority] self.cli_set(prio_base + ['match', f'prio-{priority}', 'ip', 'destination', 'port', str(1000 + int(priority))]) # commit changes self.cli_commit() def test_08_random_detect(self): self.skipTest('tc returns invalid JSON here - needs iproute2 fix') bandwidth = 5000 first = True for interface in self._interfaces: policy_name = f'qos-policy-{interface}' if first: self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) # verify() - selected QoS policy on interface only supports egress with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) first = False self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) self.cli_set(base_path + ['policy', 'random-detect', policy_name, 'bandwidth', str(bandwidth)]) bandwidth += 1000 # commit changes self.cli_commit() bandwidth = 5000 for interface in self._interfaces: tmp = get_tc_qdisc_json(interface) import pprint pprint.pprint(tmp) def test_09_rate_control(self): bandwidth = 5000 burst = 20 latency = 5 first = True for interface in self._interfaces: policy_name = f'qos-policy-{interface}' if first: self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) # verify() - selected QoS policy on interface only supports egress with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) first = False self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) self.cli_set(base_path + ['policy', 'rate-control', policy_name, 'bandwidth', str(bandwidth)]) self.cli_set(base_path + ['policy', 'rate-control', policy_name, 'burst', str(burst)]) self.cli_set(base_path + ['policy', 'rate-control', policy_name, 'latency', str(latency)]) bandwidth += 1000 burst += 5 latency += 1 # commit changes self.cli_commit() bandwidth = 5000 burst = 20 latency = 5 for interface in self._interfaces: tmp = get_tc_qdisc_json(interface) self.assertEqual('tbf', tmp['kind']) self.assertEqual(0, tmp['options']['mpu']) # TC store rates as a 32-bit unsigned integer in bps (Bytes per second) self.assertEqual(int(bandwidth * 125), tmp['options']['rate']) bandwidth += 1000 burst += 5 latency += 1 def test_10_round_robin(self): qos_config = { '1' : { 'match4' : { 'ssh' : { 'dport' : '22', }, }, }, '2' : { 'match6' : { 'ssh' : { 'dport' : '22', }, }, }, } first = True for interface in self._interfaces: policy_name = f'qos-policy-{interface}' if first: self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) # verify() - selected QoS policy on interface only supports egress with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) first = False self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) for qos_class, qos_class_config in qos_config.items(): qos_class_base = base_path + ['policy', 'round-robin', policy_name, 'class', qos_class] if 'match4' in qos_class_config: for match, match_config in qos_class_config['match4'].items(): if 'dport' in match_config: self.cli_set(qos_class_base + ['match', match, 'ip', 'destination', 'port', match_config['dport']]) if 'match6' in qos_class_config: for match, match_config in qos_class_config['match6'].items(): if 'dport' in match_config: self.cli_set(qos_class_base + ['match', match, 'ipv6', 'destination', 'port', match_config['dport']]) # commit changes self.cli_commit() for interface in self._interfaces: import pprint tmp = get_tc_qdisc_json(interface) self.assertEqual('drr', tmp['kind']) for filter in get_tc_filter_json(interface, 'ingress'): # bail out early if filter has no attached action if 'options' not in filter or 'actions' not in filter['options']: continue for qos_class, qos_class_config in qos_config.items(): # Every flowid starts with ffff and we encopde the class number after the colon if 'flowid' not in filter['options'] or filter['options']['flowid'] != f'ffff:{qos_class}': continue ip_hdr_offset = 20 if 'match6' in qos_class_config: ip_hdr_offset = 40 self.assertEqual(ip_hdr_offset, filter['options']['match']['off']) if 'dport' in match_config: dport = int(match_config['dport']) self.assertEqual(f'{dport:x}', filter['options']['match']['value']) def test_11_shaper(self): bandwidth = 250 default_bandwidth = 20 default_ceil = 30 class_bandwidth = 50 class_ceil = 80 dst_address = '192.0.2.8/32' for interface in self._interfaces: shaper_name = f'qos-shaper-{interface}' self.cli_set(base_path + ['interface', interface, 'egress', shaper_name]) self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'bandwidth', f'{bandwidth}mbit']) self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'bandwidth', f'{default_bandwidth}mbit']) self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'ceiling', f'{default_ceil}mbit']) self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'queue-type', 'fair-queue']) self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '23', 'bandwidth', f'{class_bandwidth}mbit']) self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '23', 'ceiling', f'{class_ceil}mbit']) self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '23', 'match', '10', 'ip', 'destination', 'address', dst_address]) bandwidth += 1 default_bandwidth += 1 default_ceil += 1 class_bandwidth += 1 class_ceil += 1 # commit changes self.cli_commit() bandwidth = 250 default_bandwidth = 20 default_ceil = 30 class_bandwidth = 50 class_ceil = 80 for interface in self._interfaces: config_entries = ( f'root rate {bandwidth}Mbit ceil {bandwidth}Mbit', f'prio 0 rate {class_bandwidth}Mbit ceil {class_ceil}Mbit', f'prio 7 rate {default_bandwidth}Mbit ceil {default_ceil}Mbit' ) output = cmd(f'tc class show dev {interface}') for config_entry in config_entries: self.assertIn(config_entry, output) bandwidth += 1 default_bandwidth += 1 default_ceil += 1 class_bandwidth += 1 class_ceil += 1 if __name__ == '__main__': unittest.main(verbosity=2)