Directing traffic with ALTQ

Table of Contents
ALTQ - prioritizing by traffic type
ALTQ - allocation by percentage
ALTQ - handling unwanted traffic

ALTQ - short for ALTernate Queueing - is a very flexible mechanism for directing network traffic which lived a life of its own before getting integrated into PF. Altq was another one of those things which were integrated into PF because of the additional convenience it offered when integrated.

NoteNote: OpenBSD traffic shaping uses a different, simpler syntax
 

The new traffic shaping framework hinted at by commits as far back as the development phase for OpenBSD 5.0 was finally introduced in OpenBSD 5.5 and from OpenBSD 5.6 it's the only traffic shaping option available. If you want a gentler introduction to the new traffic shaping system than what's offered by the official documentation, please see the relevant parts of The Book of PF (3rd edition) or see the slides matching my latest tutorial session.

ALTQ uses the term queue about the main traffic control mechanisms. Queues are defined with a defined amount of bandwidth or a specific part of available bandwidth, where a queue can be assigned subqueues of various types.

To complete the picture, you write filtering rules which assign packets to specified queues or a selection of subqueues where packets pass according to specified criteria.

Queues are created with one of several queue disciplines. The default queue discipline without ALTQ is FIFO (first, in first out).

A slightly more interesting discipline is the class based discipline (CBQ), which in practical terms means you define the queue's bandwidth as a set amount of data per second, as a percentage or in units of kilobits, megabits and so on, with an additional priority as an option, or priority based (priq), where you assign priority only.

Priorities can be set at 0 to 7 for cbq queues, 0 to 15 for priq queues, with a higher value assigning a higher priority and preferential treatment. In addition, the hierarchical queue algorithm "Hierarchical Fair Service Curve" or HFSC is available.

Briefly, a simplified syntax is

altq on interface type [options ... ] main_queue { sub_q1, sub_q2 ..}
  queue sub_q1 [ options ... ]
  queue sub_q2 [ options ... ]
[...]
pass [ ... ] queue sub_q1
pass [ ... ] queue sub_q2

If you will be using these features in you own rule sets, you should under any circumstances read the pf.conf man page and the PF user guide. These documents offer a very detailed and reasonably well laid out explanation of the syntax and options.[1] [2]

ALTQ - prioritizing by traffic type

Our first real example is lifted from Daniel Hartmeier's web. Like quite a few of us, Daniel is on an asymmetric connection, and naturally he wanted to get better bandwidth utilization.

One symptom in particular seemed to indicate that there was room for improvement. Incoming traffic (downloads) apparently slowed down outgoing traffic.

Analyzing the data indicated that the ACK packets for each data packet transferred caused a disproportionately large slowdown, possibly due to the FIFO (First In, First Out) queue discipline in effect on the outgoing traffic.

A testable hypothesis formed - if the tiny, practically dataless ACK packets were able to slip inbetween the larger data packets, this would lead to a more efficient use of available bandwidth. The means were two queues with different priorities. The relevant parts of the rule set follows:

ext_if="kue0"

altq on $ext_if priq bandwidth 100Kb queue { q_pri, q_def }
queue q_pri priority 7
queue q_def priority 1 priq(default)

pass out on $ext_if proto tcp from $ext_if to any flags S/SA \
        keep state queue (q_def, q_pri)

pass in  on $ext_if proto tcp from any to $ext_if flags S/SA \
        keep state queue (q_def, q_pri)

The result was indeed better performance.

So why does this work?

So why does this work?[3] The reason lies in how the ALTQ code treats subqueues with different priorities. Once a connection is assigned to the main queue, ALTQ inspects each packet's type of service (ToS) field. ACK packets have the ToS Delay bit set to 'low', which indicates that the sender wanted the speediest delivery possible.

When ALTQ sees a low delay packet and queues of differing priorities are available, it will assign the packet to the higher priority queue. This means that the ACK packets skip ahead of the lower priority queue and are delivered more quickly, which in turn means that data packets are serviced more quickly, too.

Daniel's article is available from his web site at http://www.benzedrine.cx/ackpri.html. Also see an equivalent example using the new syntax.

Using a match Rule for Queue Assignment

Starting with OpenBSD 4.6, it is possible to assign traffic to queues using match rules as well as pass rules. This makes it extremely easy to add an ALTQ regime to an existing rule set. In order to apply the priority scheme we just presented to an existing rule set on OpenBSD 4.6 or newer, you would add teh altq definition block at the top of the rule set, followed by the following rule:

match out on $ext_if from $int_if:network queue (q_def, q_pri)

If your gateway does NAT, it could also be useful to rewrite the match rule that applies the nat-to to do the queue assignment as well (simply tack on the queue assignment at the end).

Notes

[1]

On FreeBSD, ALTQ requires the ALTQ and queue discipline options for the disciplines you want to use to be compiled into the running kernel. Refer to the PF chapter of the FreeBSD Handbook for further information.

[2]

At the time of writing, ALTQ was integrated in the NetBSD's PF in time for the 4.0 release. For up to date information on this see The NetBSD PF documentation.

[3]

Earlier versions of this tutorial left the explanation pretty much as an exercise to the reader.