As no one more knowledgable has stepped up I'll offer what I can. Disclaimer: I use tc and have been able to get it to do what I want in most cases, but make no claim to being expert or even proficient. What follows is from notes made during my own learning experience with tc.
I found online tutorials for tc to be woefully lacking and some even incorrect. But I would recommend the following on the basis that I found them valuable to my own understanding:
ArchWiki:
Advanced Traffic Control
Hierachical token bucket theory
Linux.com
QoS and Traffic Control with Linux tc
And of course man tc (much more useful that it may seem on first look!).
By way of a working example with commentary, here is my own offering using eth0 as the target interface. It will follow this diagram borrowed from the above link, QoS and Traffic Control, which shows the relationships among the parts we will create:
Code:
.-------------------------------------------------------.
| |
| HTB |
| |
| .----------------------------------------------------.|
| | ||
| | Class 1:1 ||
| | ||
| | .---------------..---------------..---------------.||
| | | || || |||
| | | Class 1:10 || Class 1:20 || Class 1:30 |||
| | | || || |||
| | | .------------.|| .------------.|| .------------.|||
| | | | ||| | ||| | ||||
| | | | fq_codel ||| | fq_codel ||| | fq_codel ||||
| | | | ||| | ||| | ||||
| | | '------------'|| '------------'|| '------------'|||
| | '---------------''---------------''---------------'||
| '----------------------------------------------------'|
'-------------------------------------------------------'
First, we will flush any existing tc rules that may be laying around from previous experimentation:
Code:
#Remove all existing qdiscs, classes and filters from interface
tc qdisc del dev eth0 ingress 2>/dev/null
tc qdisc del dev eth0 root 2>/dev/null
We will not add any ingress rules here, so you may skip that one as appropriate, but be sure to start with a clean slate on egress (root).
Next we need to add a
root qdisc (
queueing discipline) within which to build our rules:
Code:
# 1) Add/Replace root qdisc of eth0 with an HTB instance,
# specify handle so it can be referred to by other rules,
# set default class for all unclassified traffic
tc qdisc replace dev eth0 root handle 1: htb default 30
Next we add a top level class to determine the total bandwidth available. You want to set this slightly below the actual bandwidth the interface or downstream path can support in order to more or less guarantee that tc remains in control (it can only restrict after all). This bucket will fill at the specified rate and all child buckets (classes) will fill from this one in their priority order.
Code:
# 2) Create single top level class with handle 1:1 which limits
# total traffic to slightly less than the path max
# For this example we assume 100mbit max and limit to 95mbit
tc class add dev eth0 parent 1: classid 1:1 htb rate 95mbit
Now we add
child classes with their own max bandwidth limits, and assign them a priority. The highest priority buckets (i.e. lowest number) are filled first, with lower priority buckets filling from what is left over. This guarantees that the highest priority classes always get their allocated bandwidth and lower priority buckets are throttled when necessary to provide that.
Code:
# 3) Create child classes for different uses:
# Class 1:10 is our outgoing highest priority path, outgoing SSH/SFTP in this example
# Class 1:20 is our next highest priority path, web admin traffic for example
# Class 1:30 is default and has lowest priority but highest total bandwidth - bulk web traffic for example
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 1mbit ceil 20mbit prio 1
tc class add dev eth0 parent 1:1 classid 1:20 htb rate 1mbit ceil 20mbit prio 2
tc class add dev eth0 parent 1:1 classid 1:30 htb rate 1mbit ceil 95mbit prio 3
In the absence of any
filters all traffic will use the default path and share the total bandwidth. We will add filters below to direct priority traffic to the priority paths.
By default HTB will attach a
leaf qdisc of type
pfifo, but there are others available (
man tc -> see also...). Some authors say
fq_codel (Fair Queuing with Controlled Delay) is worth the effort so we will add that here, but this step is optional.
Code:
# 4) Attach a leaf qdisc to each child class
# HTB by default attaches pfifo as leaf so this is optional.
# fq_codel is said to be worth the effort.
tc qdisc add dev eth0 parent 1:10 fq_codel
tc qdisc add dev eth0 parent 1:20 fq_codel
tc qdisc add dev eth0 parent 1:30 fq_codel
Finally, we need to add
filters to direct selected traffic into the desired paths. One or more filters may attach to a parent qdisc and all traffic passing through a qdisc passes through its filters, allowing a hierarchy of filters. Filters may specify many criteria for matching traffic and which class matching traffic is sent to. Here we will create only two filters, one for our high priority outgoing SSH/SFTP traffic and another for selected admin web traffic, which we will match by iptables
MARK values.
Code:
# 5) Add filters for priority traffic
tc filter add dev eth0 parent 1: handle 100 fw classid 1:10
tc filter add dev eth0 parent 1: handle 200 fw classid 1:20
In the above rules
handle sets the value to match and
fw specifies the thing to be matched, firewall mark, which matches
iptables MARK or
CONNMARK values. See
man tc-fw for more information.
The result is that all packets
MARKed 100 by iptables will be sent to the highest priority path,
class 1:10, those
MARKed 200 will follow
class 1:20, and all other traffic will follow the default lowest priority path,
class 1:30.
All that is left is to MARK outgoing traffic using iptables rules. Here are a couple of examples that can be used with the above filters, adapt to your needs, of course:
Code:
iptables -t mangle -A OUTPUT -p tcp --match multiport --dports 22,2222 -j MARK --set-mark 100
iptables -t mangle -A OUTPUT -p tcp --dst ${admin_ip} --match multiport --sports 80,443 -j MARK --set-mark 200
Now all outgoing SSH/SFTP traffic destined for eth0 will be guaranteed 20mbit bandwidth regardless of other traffic on the server. Outgoing HTTP/S destined for our admin IP will be similarly guaranteed its allocated bandwidth. All other traffic will compete for what is left after those have been satisfied.
I hope that this will be a useful example (and that I have made no obvious errors!).
Tc is really useful and not overly mysterious once you learn its terminology, and how it actually works. Read the man page until it makes sense and you will find it actually quite useful - and explore all the "see also" entries!
Good luck!
UPDATED:
I forgot to add a few simple commands for checking the status of your tc qdiscs/classes/filters:
Code:
tc qdisc ls dev eth0
tc -s qdisc ls dev eth0
tc class ls dev eth0
tc -s class ls dev eth0
tc -s -g class ls dev eth0
tc filter ls dev eth0
Other options available for these, see the fine man pages!