DSCP based QoS with HTB
Contents
DSCP based QoS with HTB
About
This page tries to describe a way to prioritize traffic by using DSCP tags. The DiffServ Code Point is a field in the IP header that allows you to classify traffic. DSCP is meant to be administered in a per-hob-based way, allowing each router on a path to determine how each traffic class should be prioritized. The solution described in this document is built around the Hierarchical Token Bucket queuing algorithm in RouterOS, dividing the 64 possible DSCP code values into the 8 queues available. This solution also utilizes the tree-based queuing, in order to have a parent queue do bandwidth control, with sub-queues for each possible DSCP value.
The actual queuing is done as per this table:
| Name | Precendence | DSCP Range | HTB Priority |
|---|---|---|---|
| Routing (default) | 000 (0) | 000000(0) – 000111 (7) | 8 |
| Priority | 001 (1) | 001000 (8) – 001111 (15) | 7 |
| Immediate | 010( (2) | 010000 (16) – 010111 (23) | 6 |
| Flash | 011 (3) | 011000 (24) – 011111 (31) | 5 |
| Flash Override | 100 (4) | 100000 (32) – 100111 (39) | 4 |
| Critical | 101 (5) | 101000 (40) – 101111 (47) | 3 |
| Internetwork Control | 110 (6) | 111000 (48) – 110111 (55) | 2 |
| Network Control | 111 (7) | 111000 (56) – 111111 (63) | 1 |
This solution has been tested on RB450, RB600 and RB1000 using any 3.x interface.
DSCP marking/mangling
In order to match DSCP values in your queues, it is necessary to mark the packets using firewall mangling. This is best done with this command:
:for x from 0 to 63 do={/ip firewall mangle add action=mark-packet chain=postrouting \
comment=("dscp_" . $x . "_eth") disabled=no dscp=$x new-packet-mark=("dscp_" . $x . "_eth") passthrough=no}
This command creates 64 lines under /ip firewall mangle, that simply marks each packet with a DSCP value to be processed later.
Having that done, it's time to move on to the actual queues.
Set up the queue tree
The next example assumes that ether1 is the wan interface, and your available bandwidth is 5Mbit/s.
/queue tree
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=0 max-limit=5000000 name=ether1 \
parent=ether1 queue=default
#prio8
:for z from 0 to 7 do={/queue tree add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=0 max-limit=0 \
name=("routine_" . $z . "_ether1") packet-mark=("dscp_" . $z . "_eth") parent=ether1 priority=8 queue=ethernet-default}
#prio7
:for z from 8 to 15 do={/queue tree add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=0 max-limit=0 \
name=("priority_" . $z . "_ether1") packet-mark=("dscp_" . $z . "_eth") parent=ether1 priority=7 queue=ethernet-default}
#prio 6
:for z from 16 to 23 do={/queue tree add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=0 max-limit=0 \
name=("immediate_" . $z . "_ether1") packet-mark=("dscp_" . $z . "_eth") parent=ether1 priority=6 queue=ethernet-default}
#prio 5
:for z from 24 to 31 do={/queue tree add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=0 max-limit=0 \
name=("flash_" . $z . "_ether1") packet-mark=("dscp_" . $z . "_eth") parent=ether1 priority=5 queue=ethernet-default}
#prio 4
:for z from 32 to 39 do={/queue tree add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=0 max-limit=0 \
name=("flash_override_" . $z . "_ether1") packet-mark=("dscp_" . $z . "_eth") parent=ether1 priority=4 queue=ethernet-default}
#prio 3
:for z from 40 to 47 do={/queue tree add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=0 max-limit=0 \
name=("critical_" . $z . "_ether1") packet-mark=("dscp_" . $z . "_eth") parent=ether1 priority=3 queue=ethernet-default}
#prio 2
:for z from 48 to 55 do={/queue tree add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=0 max-limit=0 \
name=("intercon_" . $z . "_ether1") packet-mark=("dscp_" . $z . "_eth") parent=ether1 priority=2 queue=ethernet-default}
#prio 1
:for z from 56 to 63 do={/queue tree add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=0 max-limit=0 \
name=("netcon_" . $z . "_ether1") packet-mark=("dscp_" . $z . "_eth") parent=ether1 priority=1 queue=ethernet-default}
Remarks
This solution is the most flexible solution I could come up with. It is built around the philosophy that highest DSCP marking is served first. The actual shaping of the interface could be moved to a simple queue in order to be able to police differently on upstream and downstream, but I prefer to shape in both ends of a circuit, so when you have different upload and download speed, you should shape in according to the upload speed.
Further Refinements by BrotherDust
Using the former script as a starting point, I have devised the following script:
#Set interface here
:global outboundInterface "ether1"
#Set bandwidth of the interface (remember, this is for OUTGOING)
:global interfaceBandwidth 0
#Set where in the chain the packets should be mangled
:global mangleChain postrouting
#Don't mess with these. They set the parameters for what is to follow
:global queueName ("qos_" . $outboundInterface)
:global qosClasses [:toarray "netcon,intercon,critical,flash_override,flash,immedate,priority,routine"]
:global qosIndex 64
#Set up mangle rules for all 64 DSCP marks
#This is different in that the highest priority packets are mangled first.
:for indexA from 63 to 0 do={
/ip firewall mangle add \
action=mark-packet \
chain=$mangleChain \
comment=("dscp_" . $indexA) \
disabled=no \
dscp=$indexA \
new-packet-mark=("dscp_" . $indexA) \
passthrough=no
}
#Add a base queue to the queue tree for the outbound interface
/queue tree add \
max-limit=$interfaceBandwidth \
name=$queueName \
parent=$outboundInterface \
priority=1
#Set up queues in queue tree for all 64 classes, subdivided by 8.
:for indexA from=0 to=7 do={
:local subClass ([:pick $qosClasses $indexA] . "_" . $outboundInterface)
/queue tree add \
name=$subClass \
parent=$queueName \
priority=($indexA+1) \
queue=ethernet-default
:for indexB from=0 to=7 do={
:set qosIndex ($qosIndex-1)
/queue tree add \
name=($subClass . "_" . $indexB) \
parent=$subClass \
priority=($indexB+1) \
packet-mark=("dscp_" . $qosIndex) \
queue=ethernet-default
}
}
Set the variables accordingly on the globals, paying attention to the comments. This script creates an even more granular priority structure by creating 64 different priorities subdivided by 8 master priorities. So, this is what it will look like this under interface queues when you enter it in the console:
Some usage notes:
1. Remember! The way that this script is set up by default is such that it will only work with outgoing traffic. It's best practices (in my opinion) to keep it set up that way as doing it for incoming traffic would be redundant.
2. If this going to be applied to more than one interface, cut the script up so that it doesn't make the mangle rules again.
3. Bandwidth parameter need not be set. It's just for if you have an interface with fixed bandwidth or you you want to limit that interface. If it is set it must be in bits per second. I have not yet tested this on a wireless interface because the rates are unstable and I want them to be as fast as possible.
Updated on 20090604: I changed the script slightly to reverse the mangling chain. Now highest priority packets are processed first. Probably not going to make a huge difference. But we'll see.
