Quality of Service on Linux is implemented via the kernel's traffic control subsystem (tc), part of the iproute2 package. Understanding how it works requires understanding queuing disciplines (qdiscs), classes, and filters — and how they compose into a traffic shaping hierarchy.
The tc architecture
Every network interface has a root qdisc attached. By default this is pfifo_fast — a simple three-band priority queue. Traffic shaping replaces this with a classful qdisc that can divide bandwidth across traffic classes.
# Attach HTB root qdisc to eth0
tc qdisc add dev eth0 root handle 1: htb default 30
# Create parent class (total bandwidth ceiling)
tc class add dev eth0 parent 1: classid 1:1 htb rate 100mbit
# High-priority class: VoIP and video (guaranteed 20mbit, burst to 100mbit)
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 20mbit ceil 100mbit prio 1
# Normal traffic class (guaranteed 70mbit)
tc class add dev eth0 parent 1:1 classid 1:20 htb rate 70mbit ceil 100mbit prio 2
# Bulk/background class (guaranteed 10mbit)
tc class add dev eth0 parent 1:1 classid 1:30 htb rate 10mbit ceil 30mbit prio 3
HTB: Hierarchical Token Bucket
HTB models bandwidth as token buckets arranged in a hierarchy. Each class has:
- rate: guaranteed minimum bandwidth (tokens refill at this rate)
- ceil: maximum bandwidth (class can borrow up to this from parent)
- burst: maximum burst size before rate limiting kicks in
When a class has unused tokens, sibling classes can borrow from the parent. This allows high-priority traffic to use idle bandwidth while guaranteeing minimums under contention.
SFQ: Stochastic Fairness Queuing
Within each HTB class, SFQ provides per-flow fairness. It hashes flows (by src/dst IP+port) into buckets and round-robins between them, preventing any single flow from monopolising the class's allocated bandwidth.
# Attach SFQ to each leaf class
tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10
tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10
tc qdisc add dev eth0 parent 1:30 handle 30: sfq perturb 10
perturb 10 re-randomises the hash every 10 seconds to prevent persistent hash collisions from starving a flow.
Classifying traffic into HTB classes
Filters match packets and assign them to classes. Common matching criteria:
# Classify VoIP traffic (UDP/5060 SIP + RTP ports) to high-priority class
tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 \
match ip dport 5060 0xffff flowid 1:10
# Classify by DSCP EF (Expedited Forwarding) mark
tc filter add dev eth0 parent 1:0 protocol ip prio 2 u32 \
match ip tos 0xb8 0xfc flowid 1:10
DSCP marking for downstream enforcement
A UTM appliance can mark outbound packets with DSCP values so that downstream routers (e.g., ISP edge equipment supporting DiffServ) can also apply prioritisation. CacheGuard writes DSCP marks to the ToS field:
# Mark real-time traffic with DSCP EF (46 = 0xb8 in ToS byte)
iptables -t mangle -A POSTROUTING -p udp --dport 5060 -j DSCP --set-dscp 46
Note: ISPs may strip or ignore DSCP marks at their edge. Upstream DSCP enforcement is only reliable on networks you control end-to-end.
PRIO qdisc for management traffic
For traffic destined to the appliance itself (SSH, web GUI), HTB adds latency because tokens must be available before packets are dequeued. PRIO qdisc uses strict priority bands with no token accounting — management packets are always dequeued first.
# Strict priority for management interface traffic
tc qdisc add dev lo root handle 2: prio bands 3
CacheGuard implements HTB + SFQ on the external interface and PRIO for management traffic via iproute2, configurable through the web interface without direct tc command access.
→ https://www.cacheguard.com/what-is-qos/
Originally published on the CacheGuard Blog. CacheGuard is free and open source — GitHub.

Top comments (0)