FastTrack-Friendly QoS Script

Hi all,

Here is my FastTrack-friendly QoS script, based on one made by IntrusDave. It supports IPv4 and IPv6, and uses DSCP markings to match packets. It is fasttrack-friendly, but does not require fasttrack.

The mappings of IP precedence values to queues is in line with Cisco’s AVVID 802.1p UP-Based Traffic Types, used for wireless networks, and compatible with wireless priority from MikroTik and other vendors

To retain compatibility with FastTrack, it uses interface-attached HTB rather than global-attached (fasttracked packets bypass global HTB, but are still queued by interface HTB). The inbound WAN (download) rate queueing is implemented as outbound queueing on the internal interface, since interface-attached HTB only works for egress. This is fine for many home-use scenarios where there is only one LAN subnet and only one WAN subnet.

With this solution, any traffic that is left at DSCP 0 (Best Effort) will be fasttracked; other traffic will need to bypass fasttrack to get proper priority. Of course, in many cases, most traffic will not be tagged already. If you need to manually tag traffic, this can be done by bypassing fasttrack on only the packets you need to tag (by creating an ‘accept’ rule for that traffic (also matching connection-state=established,related) just above the fasttrack rule). Then you can add mangle rules to set DSCP tags on those packets, and place those mangle rules at the top of the list, above the other mangle rules created by this script.

You’ll need to copy and paste the script into scripts (system->scripts->new (+)->paste), change the upload and download bandwidth and inbound and outbound interface names at the top to match your settings, and run the script. (the bandwidths should be slightly less than what you normally receive as your maximum)

# this is based on IntrusDave's QoS script, but modified
# qosClasses are largely based on Cisco Wireless QoS mappings/guide

#Set outbound (WAN) interface here
:local outboundInterface "ether1"

#Set UPLOAD bandwidth of the outbound (WAN) interface
:local outInterfaceBandwidth 4900k

#Set inbound (LAN) interface here
:local inboundInterface "bridge"

#Set DOWNLOAD bandwidth of the outbound (WAN) interface
:local inInterfaceBandwidth 34500k

#Set type of queue here
:local queueType wireless-default

#Set where in the chain the packets should be mangled
:local mangleChain postrouting

#Don't mess with these. They set the parameters for what is to follow
:local queueName ("QoS_" . $outboundInterface)
:local inQueueName ("QoS_" . $inboundInterface)
# qosClasses from highest to lowest priority
:local qosClasses [:toarray "Network Control (Top Priority),Internetwork Control (High Priority),Voice (Medium-High Priority),Interactive Video (Medium Priority),Critical Data or Call Signaling (Medium-Low Priority),Best Effort (Low Priority),Background (Very Low Priority),Scavenger (Bottom Priority)"]
# maps queue priorities from highest to lowest to IP precedence values
:local priorityToIpPrecedenceMappings [:toarray "7,6,5,4,3,0,2,1"]
# queue priority used for best effort traffic (IP precedence 0)
:local beQueuePriority 6

/ip firewall mangle add action=set-priority \
   chain=postrouting new-priority=from-dscp-high-3-bits \
   passthrough=yes comment="Respect DSCP tagging"
/ip firewall mangle add action=set-priority \
   chain=postrouting new-priority=6 packet-size=0-123 \
   passthrough=yes protocol=tcp tcp-flags=ack comment="Prioritize ACKs"
/ip firewall mangle add action=accept \
   chain=postrouting priority=0 \
   comment="IP Precedence (aka Packet Priority) 0 - Best Effort (Low Priority) (default)"


:for indexA from 1 to 7 do={
    :local qosIndex (7-$indexA)
    # skip best effort in list
    :if ($indexA <= (8-$beQueuePriority)) do={ :set qosIndex (8-$indexA) }
    :local subClass ([:pick $qosClasses $qosIndex] )
    /ip firewall mangle add action=mark-packet chain=$mangleChain comment=("IP Precedence (aka Packet Priority) " . $indexA . " - " . $subClass . " (apply packet mark ip_precedence_" . $indexA . ")") \
         disabled=no priority=($indexA) new-packet-mark=("ip_precedence_" . $indexA) passthrough=no
}

:if ([/system package find name=ipv6 disabled=no] = "") do={
    :log info "IPv6 package is not installed - skipping IPv6 mangle rules";
} else={

   :for dscpValue from 0 to 7 do={
   /ipv6 firewall mangle add action=accept \
      chain=postrouting dscp=$dscpValue \
      comment="IP Precedence 0 (DSCP $dscpValue) - Best Effort (Low Priority) (default)"
   }

   :for indexA from 1 to 7 do={
       :local qosIndex (7-$indexA)
       # skip best effort in list
       :if ($indexA <= (8-$beQueuePriority)) do={ :set qosIndex (8-$indexA) }
       :local subClass ([:pick $qosClasses $qosIndex] )
       :for dscpValue from ($indexA*8) to (($indexA*8)+7) do={
       /ipv6 firewall mangle add action=mark-packet chain=$mangleChain comment=("IP Precedence " . $indexA . " (DSCP " . $dscpValue . ") - " . $subClass . " (apply packet mark ip_precedence_" . $indexA . ")") \
            disabled=no dscp=$dscpValue new-packet-mark=("ip_precedence_" . $indexA) passthrough=no
       }
   }

}

/queue tree add max-limit=$outInterfaceBandwidth name=$queueName parent=$outboundInterface comment="Uplink QoS" queue=$queueType
:for queuePriority from=1 to=8 do={
   :local qosIndex ($queuePriority-1)
   :local subClass ([:pick $qosClasses $qosIndex] )
   :local ipPrecedence ([:pick $priorityToIpPrecedenceMappings $qosIndex])
   :local ipPrecedenceMark ("ip_precedence_" . $ipPrecedence)
   :if ($ipPrecedence = "0") do={ :set ipPrecedenceMark ("no-mark") }
   /queue tree add \ 
      name=("IP Precedence " . $ipPrecedence . ". " . $subClass . " - " . $outboundInterface ) \
      parent=$queueName \
      priority=($queuePriority) \
      queue=$queueType \
      packet-mark=$ipPrecedenceMark \
      comment=("Queue Priority " . $queuePriority)
}

/queue tree add max-limit=$inInterfaceBandwidth name=$inQueueName parent=$inboundInterface comment="Downlink QoS" queue=$queueType
:for queuePriority from=1 to=8 do={
   :local qosIndex ($queuePriority-1)
   :local subClass ([:pick $qosClasses $qosIndex] )
   :local ipPrecedence ([:pick $priorityToIpPrecedenceMappings $qosIndex])
   :local ipPrecedenceMark ("ip_precedence_" . $ipPrecedence)
   :if ($ipPrecedence = "0") do={ :set ipPrecedenceMark ("no-mark") }
   /queue tree add \ 
      name=("IP Precedence " . $ipPrecedence . ". " . $subClass . " - " . $inboundInterface ) \
      parent=$inQueueName \
      priority=($queuePriority) \
      queue=$queueType \
      packet-mark=$ipPrecedenceMark \
      comment=("Queue Priority " . $queuePriority)
}

Then, if you are using fasttrack, to allow DSCP-tagged packets to be classified properly, bypass fast-track for those packets by adding the following:

/ip firewall filter add action=accept chain=forward comment="Bypass fasttrack for non-zero DSCP" connection-state=established,related dscp=!0

Move that filter just BEFORE your fast-track rule! All DSCP-marked packets will then be queued appropriately, and DSCP 0 packets fast-tracked. Fast-tracked packets are all placed in the best effort queue.

Thanks for your script. I change the necessary values in your script just like you said. My network use PPPOE connection to my ISP. So, I change the outbound interface to PPPOE, which is the name of the interface I set in my configuration. It is actually virtual interface which use ether 1 interface. I set upload speed to 512k and download speed to 1024k. I think I can see the significant improvements in my network. However, I think some of the mangle rules are not match. Because I look at the bytes. Some of the mangle rules not match yet. Why? The rules not match are,

Background
Critical Data
Interactive Video
Voice
Network Control

They seems to not match. Why?

Please help me.

This matches traffic that is already tagged with DSCP tags. If your traffic has no tag, you can create your own mangle rules to match the traffic and apply a relevant DSCP tag at the top of the mangle list, above the other rules. Then when it hits that rule, it will match the DSCP and will get placed into the proper queue.

If you use fasttrack, you might also need to create a regular firewall rule to bypass fasttrack for for the traffic you need DSCP applied to. At your low bandwidth though, you probably do not need fasttrack.

How to do? I’m very new to MikroTik. I’m not familiar with terms and options. Please guide me step by step if you can. Please. I want all of your mangle rules to match and used. Also, please check my mangle page that I attached. Thanks.
Screen Shot 2016-10-13 at 12.32.46 AM.png

Nice QoS support script.

One question though:
It’s my understanding that fasttrack skips pretty much everything in the packet forwarding process - including firewall and queues.
So wouldn’t a fasttracked connection simply skip over all of this?

Do I need to set DSCP values also in every mangles?
When I run the script, the priority 1 to 7 do not have DSCP values. Only the priority 0 have DSCP. Other 7 priority only have priority values not DSCP values.
In your codes, there is some lines with “$dscp”. What that mean? I think I have problem with DSCP values now.
Please help me. Only DSCP values.
Thanks. I don’t have any DSCP values in Priority 1 to 7. Why?

No - the priority is derived from DSCP.
Note the very first mangle rule created by this script:

add action=set-priority chain=postrouting new-priority=from-dscp-high-3-bits passthrough=yes

This means that the router will examine each IP packet after the routing decision has been made, and use the top 3 bits of the DSCP field (which itself is the top 6 bits of the TOS byte) and use those three bits to set the priority. This is the standard for DSCP anyway - it is laid out so that the top 3 bits of the various DSCP values will map to an “IP precedence” value 0..7

So basically, if a packet comes along, having an IP precedence value of 4, the mangle rule will set the packet priority to 4 as well. This priority is what the remaining mangle rules will use to match the packets and put packet marks on them.

@mducharme: One other thing I noticed is that the Best Effort mark requires both dscp=0 and priority=0. Why did you specify dscp=0 as a criteria in this rule? There are a few possible DSCP values which still map to IP precedence 0, and those packets would fall through the mangle table un-marked… which I suppose is not going to affect which queue they hit since the BE queue is matching un-marked packets, but the stickler in me sees these packets being forced to compare against all rules and not just quick-exiting at the beginning with the rest of the BE traffic.

Still. I got some mangle rules not matched. Why? You can check my above attached screenshot. Some mangles are 0 bytes. Also there is one thing. Some syntax error have in this chunk of code

:if ([/system package find name=ipv6 disabled=no] = “”) do={
:log info “IPv6 package is not installed - skipping IPv6 mangle rules”;
} else={

:for dscpValue from 0 to 7 do={
/ipv6 firewall mangle add action=accept
chain=postrouting dscp=$dscpValue
comment=“IP Precedence 0 (DSCP $dscpValue) - Best Effort (Low Priority) (default)”
}

In the final line, comment=“IP Precedence 0 (DSCP $dscpValue) - Best Effort (Low Priority) (default)”
What is that “$” sign. The system said it is wrong with red cursor.
I can only run the script when I delete that “$” sign.
I’m afraid that some functionality might be missed.
Educate me. Thanks

This is probably normal. Most traffic is going to have DSCP=0, thus you’re not going to see a lot of matches on these other priorities.
What these script rules basically do is enforce the DSCP values if they already exist on packets.

If your devices aren’t sending packets with DSCP values in them, then you must match packets by some other criteria and set the priority value yourself.

Other things that typically mark packets by default are:
SIP (typically precedence 3)
RTP (voice) - uses DSCP_46 which maps to priority 5

I noticed that ROS’s BGP and OSPF engines mark their packets with CS6 (IP precedence 6), so routing protocol traffic would get mapped to the internetwork control queue - nice!
(These are more important than voice because if they get dropped due to congestion, then network connectivity could be completely lost)

I didn’t notice it supports QoS for IPv6 as well. Awesome!

The $ means “variable” - so the script is supposed to replace $dscpValue with the actual dscp value (e.g. 46).
The line you refer to only uses it to make the comment tell you what DSCP value the rule is matching, so that line doesn’t affect functionality.

Note how long the IPv6 mangle chain is because it doesn’t support “priority from high 3 bits of TOS” action.

That IPv6 function code have some syntax error I think.
Take a look at my attached screenshot. There is red cursor over the $ sign and I can’t execute the script. After I delete the $ sign, the script run normally.
What is wrong in that code, I don’t know. Please explain.
Screen Shot 2016-10-13 at 2.37.24 AM.png

Do you have IPv6 installed on your router? If not, then don’t worry about it. The error seems to begin on the line “/ipv6 firewall” (firewall should be blue, not red - meaning that it doesn’t know what that keyword means - everything after that is messed up by virtue of the fact that the command is invalid after that point)

Also - this script is designed to be pasted into a /system script, not directly onto the command line.

I went into scripts, added a new script, and pasted this script into the Source: window, then clicked Apply and Run Script.

Now. It’s OK. Thanks for everything. It has something to do with " " and and ( ) and those kind of things. Now, it’s OK. It’s already 3:00 AM in the morning here. I have to sleep. Bye.

It is best to handle this situation in prerouting by setting the DSCP on those packets, e.g. you match on IP, proto/port, whatever
to identify your voice traffic and then set the DSCP to 46 for those. Then later on in postrouting they are treated with proper
priority. And, the DSCP is further carried with the packet so you don’t need all criteria for classifying the traffic at all your routers,
only at the edge routers that face the broken equipment/software that does not properly set DSCP itself.
(I try to do this in the sending system itself rather than in the router, but my systems are usually Linux so it is easy to do)

Yes and no.

Interface-attached queue trees work with fasttracked packets. What doesn’t work is matching the DSCP tags and marking them, so all fasttracked packets will have no mark.

The way to make this work with fasttrack is you bypass fasttrack for all packets that you need to mark as something other than “best effort” by adding firewall rules to “accept” these before they hit the fasttrack rule. All best effort packets are still fasttracked, so you still benefit from fasttrack.

See screenshot below:
fasttrack-bypass.PNG

Thanks, just fixed that. I didn’t notice the issue because I use fasttrack, so my BE packets get fasttracked and most do not hit the rule anyway.

Also, you can bypass fasttrack for any packets that have non-zero DSCP; this is helpful for matching packets marked by some other device.
fasttrack-bypass-dscp.PNG

I’ve been thinking about this some more.
In a nutshell, I think you cannot use fastpath forwarding if you want to perform QoS prioritization in your router.

If I’m missing something here, definitely point it out because my goal is to be as knowledgeable as possible.
Anyway, here we go…

This solution is definitely an efficient QoS mechanism for ROS and I like it very much, but I think that mixing fastpath and slowpath traffic in the same router will break QoS.
The hybrid methodology of skipping fasttrack in order to queue certain traffic while allowing the rest to be fasttracked would be fine for a selective rate limiting solution, but would fail for actual packet priority-based QoS.

Here’s why:

  • QoS prioritization only matters during congestion.
  • If there’s no congestion, all traffic is simply forwarded in fifo fashion and everyone’s happy. No packets are lost.
  • If there IS congestion, then QoS is the triage process by which the least important packets are dropped whenever the output buffer and output queues are all full.
  • In order to make an informed decision, the QoS mechanism must know how much bandwidth is available so that it knows when to start dropping low-priority packets in favor of important packets.

So far, so good, but here’s where the hybrid fast/slow path solution runs into trouble:

  • Fastpath skips the system queues and goes straight to the final HW queue on the interface itself. If the HW queue is full, then the packet will be dropped. If not, then it will be queued.
  • The job of the system queues is to ensure there will always be room on the HW queue for priority packets.
  • If a low-priority packet comes along which would fill the HW queue, then this packet should be dropped as long as the priority queue still has bandwidth available in its budget.
  • The tree’s root queue is how this bandwidth is tracked - child queues borrow bandwidth budget points from their parent queues.
  • The parent queue should run out of bandwidth at the same moment when the actual interface runs out of bandwidth. (or sooner)

Again - fastpath skips the system queues.

Therefore, the synchronization between the root queue of the tree is lost.

Because of this loss of sync, the queue tree can forward a low priority packet thinking there is still plenty of bandwidth remaining (fifo ftw!), unaware other flows have consumed the interface’s bandwidth which the queue tree considered to be available. In this case, a high priority packet will not be prioritized over low priority traffic in the queue trees because the tree thinks there’s still plenty of room for everyone - when in actuality there is not. Thus traffic is proceeding through the tree in a fifo manner. When the queue tree goes to place the packet (whatever its priority) onto the HW queue for egress - shock! the HW queue is full! The queue tree must drop the packet (rather, the HW queue will drop the packet)

The HW queue isn’t always where this drop happens though. It gets worse…

Suppose the router’s egress interface is a high-capacity interface (like a gigabit ethernet port) but traffic is being throttled by some other device farther upstream. If this throttling device (DSL/cable modem, ONT, Wireless radio, etc) doesn’t have any notion of QoS, then it’s just going to do fifo and discard all packets that it has no room for on the wire (or which exceed a policing rate) regardless of priority. If the bottleneck DOES honor QoS - then you’re going to be okay anyway, but this is pretty rare in most Internet access circuits. QoS-enabled circuits are usually enterprise-class offerings, and are usually internal wan links - not Internet connections because the Internet itself is a “best effort” network to begin with.

In this scenario where the bottleneck is upstream from your router, the only recourse is for your router to intelligently rate limit and prioritize the traffic BEFORE it reaches the dumb fifo such that it will always be below the fifo’s threshold - i.e. you present traffic that the dumb fifo will never need to discard. This can only be done if the master queue’s bandwidth budget is 100% accurate, and matches (or falls beneath) the actual bottleneck bandwidth amount.
THAT requires 100% of traffic to go through the queues.

Again, I love this simple architecture - and it’s great in a no-fastpath-forwarding scenario. It’s also great if you choose to do throttling - e.g.: rate limit video streaming to some maximum, while fasttracking the rest - but true priority-based QoS cannot function properly if the bandwidth budget is inaccurate.

Here I am talking about FastTrack, and not FastPath. However, what you say is not completely accurate:

FastTrack skips all simple queues - so they are useless, yes
FastTrack skips all queue trees (HTB) with parent global, so they are useless, yes

However, FastTrack DOES NOT skip any queue trees with an interface as the parent. All FastTrack’ed packets are still queued by the queue tree, not the hardware queue. The only problem is everything has no-mark because FastTrack also skips any mangle rules that would mark the packets.

If you try it out, you see every FastTrack’ed packet indeed does get processed through this queue tree as a no-mark packet.

So with this solution, all traffic, including FastTrack traffic, is still queued and rate-limited, so the QoS functions properly.

Okay - that’s good to know. And given this, even traffic which skips the mangle table (fasttracked packets) will still hit the best effort queue because that queue is configured to match un-marked packets. Thus the parent queue’s budget stays accurate.
Brilliant. That clears things up for me.

Sorry my post was so long, but I felt that I needed to explain why the issue of traffic missing the queue was an issue for prioritization (especially for those reading along who’re learning how QoS works) - I wasn’t trying to imply that you don’t know what you’re doing.