5. Packet Queueing and Prioritization

Packet Filter allows you to manage bandwidth by creating queues and assigning packets to them; OpenBSD's Packet Filter currently supports three schedulers (i.e. algorithms used to decide which packets get delayed, dropped, or sent out immediately):

For a detailed description of each algorithm, please refer to the PF User's Guide. Each scheduler has its own class in py-pf, which will be discussed in the following paragraphs.

5.1 PFAltqCBQ objects

Class Based Queueing is managed through the pf.PFAltqCBQ class:

class pf.PFAltqCBQ([altq[, **kw]])
The optional altq argument is a string containing the name of the queue; if omitted, the queue will be treated as a root queue ("altq on" directive in pf.conf(5) format); if present, the queue will be treated as a child queue ("queue" directive in pf.conf(5) format). The **kw parameter allows you to specify the value of any attribute by passing it as a keyword.

PFAltqCBQ instances have the following attributes:

PFAltqCBQ.ifname
String containing the name of the interface on which this queue is activated.
PFAltqCBQ.scheduler
A constant always equal to pf.ALTQT_CBQ.
PFAltqCBQ.qname
String containing the name of the queue, or an empty string if this is a root queue.
PFAltqCBQ.ifbandwidth
The actual bandwidth of the interface on which this queue is activated. Note: this parameter must be explicitely set for interfaces that do not know their bandwidth.
PFAltqCBQ.bandwidth
The maximum bitrate to be processed by the queue; if greater than 100, this value will be treated as an absolute value, otherwise as a percentage of the parent queue's bandwidth.
PFAltqCBQ.parent
String containing the name of the parent queue, or an empty string if this queue is a direct child of a root queue.
PFAltqCBQ.parent_qid
The numeric ID of the parent queue.
PFAltqCBQ.tbrsize
For root queues, the size, in bytes, of the token bucket regulator. If not specified, heuristics based on the interface bandwidth are used to determine the size.
PFAltqCBQ.priority
The priority level of the queue as an integer; the valid range is 0 to 7; the default is pf.DEFAULT_PRIORITY.
PFAltqCBQ.qlimit
The maximum number of packets held in the queue; the default is pf.DEFAULT_QLIMIT.
PFAltqCBQ.qid
The numeric ID of the queue.
PFAltqCBQ.optflags
A bitmask containing additional options for the queue; valid flags are: pf.CBQCLF_DEFCLASS (treat queue as default queue), pf.CBQCLF_ECN (enable Explicit Congestion Notification on queue), pf.CBQCLF_RED (enable Random Early Detection on queue), pf.CBQCLF_BORROW (the queue can borrow bandwidth from the parent).
PFAltqCBQ.opts
A dictionary of additional options, normally determined via heuristics.

5.2 PFAltqHFSC objects

Hierarchical Fair Service Curve queueing is managed through the pf.PFAltqHFSC class:

class pf.PFAltqHFSC([altq[, **kw]])
The optional altq argument is a string containing the name of the queue; if omitted, the queue will be treated as a root queue ("altq on" directive in pf.conf(5) format); if present, the queue will be treated as a child queue ("queue" directive in pf.conf(5) format). The **kw parameter allows you to specify the value of any attribute by passing it as a keyword.

PFAltqHFSC instances have the following attributes:

PFAltqHFSC.ifname
String containing the name of the interface on which this queue is activated.
PFAltqHFSC.scheduler
A constant always equal to pf.ALTQT_HFSC.
PFAltqHFSC.qname
String containing the name of the queue, or an empty string if this is a root queue.
PFAltqHFSC.ifbandwidth
The actual bandwidth of the interface on which this queue is activated. Note: this parameter must be explicitely set for interfaces that do not know their bandwidth.
PFAltqHFSC.bandwidth
The maximum bitrate to be processed by the queue; if greater than 100, this value will be treated as an absolute value, otherwise as a percentage of the parent queue's bandwidth.
PFAltqHFSC.parent
String containing the name of the parent queue, or an empty string if this queue is a direct child of a root queue.
PFAltqHFSC.parent_qid
The numeric ID of the parent queue.
PFAltqHFSC.tbrsize
For root queues, the size, in bytes, of the token bucket regulator. If not specified, heuristics based on the interface bandwidth are used to determine the size.
PFAltqHFSC.priority
The priority level of the queue as an integer; the valid range is 0 to 7; the default is pf.DEFAULT_PRIORITY.
PFAltqHFSC.qlimit
The maximum number of packets held in the queue; the default is pf.DEFAULT_QLIMIT.
PFAltqHFSC.qid
The numeric ID of the queue.
PFAltqHFSC.optflags
A bitmask containing additional options for the queue; valid flags are: pf.HFCF_DEFAULTCLASS (treat queue as default queue), pf.HFCF_ECN (enable Explicit Congestion Notification on queue), pf.HFCF_RED (enable Random Early Detection on queue).
PFAltqHFSC.rtsc
A 3-tuple specifying the bandwidth share of a backlogged queue. The format for service curve specifications is (m1, d, m2). m2 controls the bandwidth assigned to the queue; if non-zero, m1 and d control the initial bandwidth assignment: for the first d milliseconds the queue gets the bandwidth given as m1, afterwards the value given in m2.
PFAltqHFSC.lssc
A 3-tuple specifying the minimum required bandwidth for the queue, in the same format as PFAltqHFSC.rtsc.
PFAltqHFSC.ulsc
A 3-tuple specifying the maximum allowed bandwidth for the queue, in the same format as PFAltqHFSC.rtsc.

5.3 PFAltqPriQ objects

Priority Queueing is managed through the pf.PFAltqPriQ class:

class pf.PFAltqPriQ([altq[, **kw]])
The optional altq argument is a string containing the name of the queue; if omitted, the queue will be treated as a root queue ("altq on" directive in pf.conf(5) format); if present, the queue will be treated as a child queue ("queue" directive in pf.conf(5) format). The **kw parameter allows you to specify the value of any attribute by passing it as a keyword.

PFAltqPriQ instances have the following attributes:

PFAltqPriQ.ifname
String containing the name of the interface on which this queue is activated.
PFAltqPriQ.scheduler
A constant always equal to pf.ALTQT_PRIQ.
PFAltqPriQ.qname
String containing the name of the queue, or an empty string if this is a root queue.
PFAltqHFSC.ifbandwidth
The actual bandwidth of the interface on which this queue is activated. Note: this parameter must be explicitely set for interfaces that do not know their bandwidth.
PFAltqHFSC.bandwidth
The maximum bitrate to be processed by the queue; if greater than 100, this value will be treated as an absolute value, otherwise as a percentage of the parent queue's bandwidth.
PFAltqPriQ.parent
The name of the parent queue, i.e. an empty string since, in Priority Queueing, all queues are direct children of the root queue.
PFAltqPriQ.parent_qid
The numeric ID of the parent queue.
PFAltqPriQ.tbrsize
For root queues, the size, in bytes, of the token bucket regulator. If not specified, heuristics based on the interface bandwidth are used to determine the size.
PFAltqPriQ.priority
The priority level of the queue as an integer; the valid range is 0 to 15; the default is pf.DEFAULT_PRIORITY.
PFAltqPriQ.qlimit
The maximum number of packets held in the queue; the default is pf.DEFAULT_QLIMIT.
PFAltqPriQ.qid
The numeric ID of the queue.
PFAltqPriQ.optflags
A bitmask containing additional options for the queue; valid flags are: pf.PRCF_DEFAULTCLASS (treat queue as default queue), pf.PRCF_ECN (enable Explicit Congestion Notification on queue), pf.PRCF_RED (enable Random Early Detection on queue).

5.4 Packet queueing in action

This paragraph contains a few examples on how to enable packet queueing with the various schedulers, in order to get a better understanding on how these objects work. Let's start with Priority Queueing:

# Enable queueing on the external interface to control outgoing traffic.
ext_if = "fxp0"
ifbw = 610 * 1000    # Interface bandwidth (610Kbps)

# First we define the root queue:
#   altq on fxp0 priq bandwidth 610Kb
rootq = pf.PFAltqPriQ(ifname=ext_if, ifbandwidth=ifbw)

# Next we define the child queues:
#   queue std_out    priq(default)
#   queue ssh_im_out priority 4 priq(red)
#   queue dns_out    priority 5
std_outq    = pf.PFAltqPriQ("std_out", ifname=ext_if, ifbandwidth=ifbw,
                            optflags=pf.PRCF_DEFAULTCLASS)
ssh_im_outq = pf.PFAltqPriQ("ssh_im_out", ifname=ext_if, ifbandwidth=ifbw,
                            priority=4, optflags=pf.PRCF_RED)
dns_outq    = pf.PFAltqPriQ("dns_out", ifname=ext_if, ifbandwidth=ifbw,
                            priority=5)

# Load the queues on the system
filter = PacketFilter()
filter.add_altqs(rootq, std_outq, ssh_im_outq, dns_outq)

# Check that queues have been loaded correctly
for q in filter.get_altqs():
    print q

Now let's have a look at a Class Based Queueing example. Please note that, unlike Priority Queueing, CBQ and HFSC queues are arranged in an hierarchical manner (i.e. in a inverted-tree structure); therefore, in addition to the root queue that enables queueing on a specific interface, you also need to create an additional queue that will be parent for the other queues: pfctl(8) manages this automatically by creating a parent queue named "root_ifname", so you may want to stick to the same naming convention.

For child queues, the parent attribute (i.e. the name of their parent queue) must be explicitly set, for PF to correctly build the queue tree; also note that the hierarchy can be further expanded by defining queues within queues.

# Enable queueing on the internal interface to control incoming traffic.
int_if = "fxp1"
ifbw = 2 * 1000 * 1000    # Max bandwidth (2Mbps)

# First we define the root queue:
#   altq on fxp1 cbq bandwidth 2Mb 
rootq = pf.PFAltqCBQ(ifname=int_if, ifbandwidth=ifbw)

# Next we need to define the parent of all subsequent rules
parentq = pf.PFAltqCBQ(qname="root_" + int_if, ifname=int_if, ifbandwidth=ifbw,
                       bandwidth=ifbw)

# Now we can define the child queues:
#   queue std_in    bandwidth 1.6Mb cbq(default)
#   queue ssh_im_in bandwidth 200Kb priority 4
#   queue dns_in    bandwidth 120Kb cbq(borrow)
std_inq    = pf.PFAltqCBQ(qname="std_in", ifname=int_if, ifbandwidth=ifbw,
                          parent=parentq.qname, bandwidth=int(1.6*1000*1000),
                          optflags=pf.CBQCLF_DEFCLASS)
ssh_im_inq = pf.PFAltqCBQ(qname="ssh_im_in", ifname=int_if, ifbandwidth=ifbw,
                          parent=parentq.qname, bandwidth=200*1000, priority=4)
dns_inq    = pf.PFAltqCBQ(qname="dns_in", ifname=int_if, ifbandwidth=ifbw,
                          parent=parentq.qname, bandwidth=120*1000,
                          optflags=pf.CBQCLF_BORROW)

# Load the queues on the system
filter = pf.PacketFilter()
filter.add_altqs(rootq, parentq, std_inq, ssh_im_inq, dns_inq)