Page MenuHomeVyOS Platform

QoS shaper policy filters actually polices and limits bandwidth
Open, NormalPublicBUG

Description

I spent months trying to get dynamic HTB traffic shaping.
I want to have priority tiers, classes. I need high priority traffic to choke lower priority traffic to basic rate, but when no high priority traffic taking root bandwidth - low priority should upscale to full available bandwidth.

So here is my lab setup:

Lab-TC-Target      10.128.7.215                     - iperf3 server
Lab-TC-GW          10.128.7.216,   172.16.2.1/24    - VyOS GW, NAT, shaper
Lab-TC-T1                          172.16.2.2/24    - Tier 1, high priority
Lab-TC-T2                          172.16.2.3/24    - Tier 2, middle 1 priority
Lab-TC-T3                          172.16.2.4/24    - Tier 2, middle 2 priority
Lab-TC-T4                          172.16.2.5/24    - Tier 3, lowest priority
Lab-TC-T5                          172.16.2.6/24    - Tier 4, default priority

Config

# Packet marking
set policy route tc-mark interface 'eth1'

set policy route tc-mark rule 10 description 't1'
set policy route tc-mark rule 10 set mark '244010'
set policy route tc-mark rule 10 source address '172.16.2.2/32'

set policy route tc-mark rule 20 description 't2'
set policy route tc-mark rule 20 set mark '244020'
set policy route tc-mark rule 20 source address '172.16.2.3/32'

set policy route tc-mark rule 30 description 't3'
set policy route tc-mark rule 30 set mark '244030'
set policy route tc-mark rule 30 source address '172.16.2.4/32'

set policy route tc-mark rule 40 description 't4'
set policy route tc-mark rule 40 set mark '244040'
set policy route tc-mark rule 40 source address '172.16.2.5/32'

set qos traffic-match-group t1 match t1 mark '244010'
set qos traffic-match-group t1 match t2 mark '244020'
set qos traffic-match-group t2 match t3 mark '244030'
set qos traffic-match-group t3 match t4 mark '244040'
set qos traffic-match-group t4 match t5 mark '244050'


# Delete
delete qos policy shaper UPLOAD-200 

# Shaper
set qos policy shaper UPLOAD-200 bandwidth '200mbit'

# High priority
set qos policy shaper UPLOAD-200 class 10 description 'T1'
set qos policy shaper UPLOAD-200 class 10 ceiling '25mbit'
set qos policy shaper UPLOAD-200 class 10 bandwidth '25mbit'
set qos policy shaper UPLOAD-200 class 10 priority '1'
set qos policy shaper UPLOAD-200 class 10 burst '256k'
set qos policy shaper UPLOAD-200 class 10 queue-type 'fq-codel'
set qos policy shaper UPLOAD-200 class 10 match-group t1

# Default priority
set qos policy shaper UPLOAD-200 default ceiling '200mbit'
set qos policy shaper UPLOAD-200 default bandwidth '25mbit'
set qos policy shaper UPLOAD-200 default burst '128k'
set qos policy shaper UPLOAD-200 default priority '7'
set qos policy shaper UPLOAD-200 default queue-type 'fq-codel'

# Middle 1 priority
set qos policy shaper UPLOAD-200 class 20 description 'T2'
set qos policy shaper UPLOAD-200 class 20 ceiling '200mbit'
set qos policy shaper UPLOAD-200 class 20 bandwidth '10mbit'
set qos policy shaper UPLOAD-200 class 20 burst '128k'
set qos policy shaper UPLOAD-200 class 20 priority '9'
set qos policy shaper UPLOAD-200 class 20 queue-type 'fq-codel'
set qos policy shaper UPLOAD-200 class 20 match-group t2

# Middle 2 priority
set qos policy shaper UPLOAD-200 class 40 description 'T3'
set qos policy shaper UPLOAD-200 class 40 ceiling '200mbit'
set qos policy shaper UPLOAD-200 class 40 bandwidth '10mbit'
set qos policy shaper UPLOAD-200 class 40 burst '128k'
set qos policy shaper UPLOAD-200 class 40 priority '10'
set qos policy shaper UPLOAD-200 class 40 queue-type 'fq-codel'
set qos policy shaper UPLOAD-200 class 40 match-group t3

# Lowest priority
set qos policy shaper UPLOAD-200 class 70 description 'T4'
set qos policy shaper UPLOAD-200 class 70 ceiling '200mbit'
set qos policy shaper UPLOAD-200 class 70 bandwidth '10mbit'
set qos policy shaper UPLOAD-200 class 70 burst '128k'
set qos policy shaper UPLOAD-200 class 70 priority '14'
set qos policy shaper UPLOAD-200 class 70 queue-type 'fq-codel'
set qos policy shaper UPLOAD-200 class 70 match-group t4

And guess what - all leaf qdiscs (classes) was always stuck at configured bandwidth, 10mbit.
Then I digged up tc filters, and output showed this

photo_2025-10-25_02-32-32.jpg (663×920 px, 118 KB)

(Sorry, I only have screenshot at the moment).

What you can see is tc filter rules have action POLICY, with hard coded bandwidth limits according to qos class config.
In case of HTB, why statically limit bandwidth? HTB's task is to upscale and downscale queues dynamically.

Digging source code I've found that filters add policy action,
https://github.com/vyos/vyos-1x/blob/current/python/vyos/qos/base.py#L230
Why?

I see it was fixed in past,
https://github.com/vyos/vyos-1x/blob/08b333eac3c274a9111e68823e5c9de585add5e4/python/vyos/qos/base.py#L243, sever-sever commented action policy in filters, and after that commit it would have been possible to configure HTB with dynamic bandwidth scaling according to priorities, tokens.

Just after the month of commit, new change to fix policer was added
https://github.com/vyos/vyos-1x/commit/7632b7446ec45d2444cb3ccfb075c8e8b2df31b0

If this was done intentionally, then 'action policy' in base.py is also being called in shaper policy, which shouldn't happen.

I was able to achieve HTB shaping, but outside of VyOS using tc directly like this

#!/bin/bash
set -e

IF="eth0"

PARENT_RATE="200mbit"
PARENT_CEIL="200mbit"

MARK_HIGH=244010    # T1
MARK_T2=244020      # T2
MARK_T3=244030      # T3
MARK_T4=244040      # T4

RATE_HIGH="25mbit"      # class 10
RATE_T2="10mbit"        # class 20
RATE_T3="10mbit"        # class 40
RATE_T4="10mbit"        # class 70
RATE_DEFAULT="256kbit"  # class 30 (unmatched traffic)

# Ceil for all classes. Everyone can borrow up to the parent when idle
CEIL_ALL="200mbit"

PRIO_HIGH=0
PRIO_T2=1
PRIO_DEFAULT=2
PRIO_T3=3
PRIO_T4=7

QUANTUM=15140

BURST_PARENT="64k"
BURST_CHILD="64k"

# Clean
tc qdisc del dev "$IF" root    2>/dev/null || true
tc qdisc del dev "$IF" ingress 2>/dev/null || true

# Root HTB qdisc
tc qdisc add dev "$IF" root handle 1: htb default 30 r2q 10

# Root class 1:1
tc class add dev "$IF" parent 1: classid 1:1 htb \
    rate "$PARENT_RATE" ceil "$PARENT_CEIL" \
    burst "$BURST_PARENT" cburst "$BURST_PARENT"

#   1:10  = HIGH / T1
#   1:20  = T2
#   1:30  = DEFAULT
#   1:40  = T3
#   1:70  = T4 (lowest)

# High priority / T1
tc class add dev "$IF" parent 1:1 classid 1:10 htb \
    rate "$RATE_HIGH" ceil "$CEIL_ALL" \
    burst "$BURST_CHILD" cburst "$BURST_CHILD" \
    quantum "$QUANTUM" prio "$PRIO_HIGH"

# T2
tc class add dev "$IF" parent 1:1 classid 1:20 htb \
    rate "$RATE_T2" ceil "$CEIL_ALL" \
    burst "$BURST_CHILD" cburst "$BURST_CHILD" \
    quantum "$QUANTUM" prio "$PRIO_T2"

# Default
tc class add dev "$IF" parent 1:1 classid 1:30 htb \
    rate "$RATE_DEFAULT" ceil "$CEIL_ALL" \
    burst "$BURST_CHILD" cburst "$BURST_CHILD" \
    quantum "$QUANTUM" prio "$PRIO_DEFAULT"

# T3
tc class add dev "$IF" parent 1:1 classid 1:40 htb \
    rate "$RATE_T3" ceil "$CEIL_ALL" \
    burst "$BURST_CHILD" cburst "$BURST_CHILD" \
    quantum "$QUANTUM" prio "$PRIO_T3"

# T4 (lowest)
tc class add dev "$IF" parent 1:1 classid 1:70 htb \
    rate "$RATE_T4" ceil "$CEIL_ALL" \
    burst "$BURST_CHILD" cburst "$BURST_CHILD" \
    quantum "$QUANTUM" prio "$PRIO_T4"

tc qdisc add dev "$IF" parent 1:10 handle 110: fq_codel
tc qdisc add dev "$IF" parent 1:20 handle 120: fq_codel
tc qdisc add dev "$IF" parent 1:30 handle 130: fq_codel
tc qdisc add dev "$IF" parent 1:40 handle 140: fq_codel
tc qdisc add dev "$IF" parent 1:70 handle 170: fq_codel


# High / T1 traffic -> 1:10
tc filter add dev "$IF" parent 1: protocol all prio 10 handle $MARK_HIGH fw classid 1:10

# T2 traffic -> 1:20
tc filter add dev "$IF" parent 1: protocol all prio 20 handle $MARK_T2 fw classid 1:20

# T3 traffic -> 1:40
tc filter add dev "$IF" parent 1: protocol all prio 30 handle $MARK_T3 fw classid 1:40

# T4 (lowest) traffic -> 1:70
tc filter add dev "$IF" parent 1: protocol all prio 40 handle $MARK_T4 fw classid 1:70

So, what needs to be done?
Block that adds 'action policy' should be removed OR additional check if its called for shaper, not limiter (policer)

Details

Version
2025.09.17-0018-rolling
Is it a breaking change?
Behavior change
Issue type
Bug (incorrect behavior)