Prioritizing empty TCP ACKs with pf and ALTQ
Introduction
(there's a french translation of this page by Alexandre Anriot)
ALTQ is a framework to manage queueing disciplines on network interfaces.
It manipulates output queues to enforce bandwidth limits and prioritize
traffic based on classification.
While ALTQ was part of OpenBSD and has been
enabled by default since several
releases, the next release will merge the ALTQ and
pf
configuration into a
single file and let pf assign packets to queues. This both simplifies the
configuration and greatly reduces the cost of queue assignment.
This article presents a simple yet effective example of what the pf/ALTQ
combination can be used for. It's meant to illustrate the new configuration
syntax and queue assignment. The code used in this example is already
available in the -current OpenBSD source branch.
Problem
I'm using an
asymmetric DSL with 512 kbps downstream and 128 kbps upstream
capacity (minus PPPoE overhead). When I download, I get transfer rates of
about 50 kB/s. But as soon as I start a concurrent upload, the download
rate drops significantly, to about 7 kB/s.
Explanation
Even when a TCP connection is used to send data only in one direction
(like when downloading a file through ftp), TCP acknowledgements (ACKs) must
be sent in the opposite direction, or the peer will assume that its packets
got lost and retransmit them. To keep the peer sending data at the maximum
rate, it's important to promptly send the ACKs back.
When the uplink is saturated by other connections (like a concurrent upload),
all outgoing packets get delayed equally by default. Hence, a concurrent
upload saturating the uplink causes the outgoing ACKs for the download to
get delayed, which causes the drop in the download throughput.
Solution
The outgoing ACKs related to the download are small, as they don't contain
any data payload. Even a fast download saturating the 512 kbps downstream
does not require more than a fraction of upstream bandwidth for the related
outgoing ACKS.
Hence, the idea is to prioritize TCP ACKs that have no payload. The following
pf.conf fragment illustrates how to set up the queue definitions and assign
packets to the defined queues:
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)
First, a macro is defined for the external interface. This makes it easier
to adjust the ruleset when the interface changes.
Next, altq is enabled on the interface using the priq scheduler, and the
upstream bandwidth is specified.
I'm using 100 kbps instead of 128 kbps as this is the real maximum I can reach
(due to PPPoE encapsulation overhead). Some experimentation might be needed to
find the best value. If it's set too high, the priority queue is not effective,
and if it's set too low, the available bandwidth is not fully used.
Then, two queues are defined with (arbitrary) names q_pri and q_def. The queue
with the lower priority is made the default.
Finally, the rules passing the relevant connections (statefully) are extended
to specify what queues to assign the matching packets to. The first queue specified
in the parentheses is used for all packets by default, while the second (and
optional) queue is used for packets with ToS (type of service) 'lowdelay' (for
instance interactive ssh sessions) and TCP ACKs without payload.
Both incoming and outgoing TCP connections will pass by those two rules, create
state, and all packets related to the connections will be assigned to either
the q_def or q_pri queues. Packets assigned to the q_pri queue will have
priority and will get sent before any pending packets in the q_def queue.
Result
The following test was performed first without and then with the ALTQ rules
explained above:
- -10 to -8 minutes: idle
- -8 to -6 minutes: download only
- -6 to -4 minutes: concurrent download and upload
- -4 to -2 minutes: upload only
- -2 to 0 minutes: idle
The first graphs shows the results of the test without ALTQ, and the second one
with ALTQ:
The improvement is quite significant, the saturated uplink no longer delays
the outgoing empty ACKs, and the download rate doesn't drop anymore.
This effect is not limited to asymmetric links, it occurs whenever one
direction of the link is saturated. With an asymmetric link this occurs
more often, obviously.
Notes
OpenBSD 3.3-release didn't support queueing on the tun(4) interface. This has
since been fixed, see altq on tun
for a description and results.
Related links
|