Introduction
Introduction Statistics Contact Development Disclaimer Help
Title: Fair Internet bandwidth management on a network using OpenBSD
Author: Solène
Date: 30 August 2021
Tags: openbsd bandwidth
Description:
# Introduction
I have a simple DSL line with a 15 Mb/s download and 900 kb/s upload
rates and there are many devices using the Internet and two people in
remote work. Some poorly designed software (mostly on windows) will
auto update without allowing to reduce the bandwidth or some huge
bloated website will require lot of download and will impact workers
using the network.
The point of this article is to explain how to use OpenBSD as a router
on your network to allow the Internet access to be used fairly by
devices on the network to guarantee everyone they will have at least a
bit of Internet to continue working flawlessly.
I will use the queuing features from the OpenBSD firewall PF (Packet
Filter) which relies on the CoDel network scheduler algorithm, which
seems to bring all the features we need to do what we want.
pf.conf manual page: QUEUEING section
Wikipedia page about the CoDel network scheduler algorithm
# Important
I'm writing this in a separate section of the article because it is
important to understand.
It is not possible to limit the download bandwidth, because once the
data are already in the router, this mean they came from the modem and
it's too late to try to do anything. But there is still hope, if the
router receives data from the Internet it's that some devices on the
network asked to receive it, you can act on the uploaded data to
throttle what we receive. This is not obvious at first but it makes
totally sense once you get the idea.
The biggest point to understand is that you can throttle download speed
through the ACK packets. Think of two people on a phone, let's say
Alice and Bob, Alice is your network and calls Bob who is very happy to
tell his life to Alice. Bob speaking is data you download. In a
normal conversation, Bob will talk and will hear some sounds from Alice
who acknowledge what Bob is saying. If Alice stops or shut her
microphone, Bob may ask if Alice is still listening and will wait for
an answer. When Alice is making a sound (like "hmmhm or yes"), this is
an acknowledgement for Bob to continue. Literally, Bob is sending a
voice stream to Alice who is sending ACK (acknowledgement short name)
packets to Bob so he can continue.
This is exactly where you can control bandwidth, if we reduce the
bandwidth used by ACK packets for a download, you can reduce the given
download. If you can allow multiple systems to fairly send their share
of ACK, they should have a fair share of the downloaded data.
What's even more important is that you absolutely don't use all the
upload bandwidth with ACK packets to reach your maximum download
bandwidth. We will have to separate ACK from uploaded data so we don't
limit file upload or similar flows.
# Setup
For the setup I used a laptop with two network cards, one was connected
to the ISP box and the other was on the LAN side. I've enabled a DHCP
server on the OpenBSD router to automatically give IP addresses and
gateway and name servers addresses to devices on the network.
Basically, you can just plug an equivalent router on your current LAN,
disable DHCP on your ISP router and enable DHCP on your OpenBSD system
using a different subnet, both subnets will be available on the network
but for tests it requires little changes, when you want to switch from
a router to another by default, toggle the DHCP service on both and
renew DHCP leases on your devices. This is extremely easy.
```ASCII network diagram
+---------+
| ISP |
| router |
+---------+
|
|
| re0
+---------+
| OpenBSD |
| router |
+---------+
| em0
|
|
+---------+
| network |
| switch |
+---------+
```
# Configuration explained
## Line by line
I'll explain first all the config lines from my /etc/pf.conf file, and
later in this article you will find a block with the complete rules
set.
The following lines are default and can be kept as-is except if you
want to filter what's going in or out, but it's another topic as we
only want to apply queues. Filtering would be as usual.
```pf.conf configuration line
set skip on lo
block return # block stateless traffic
pass # establish keep-state
```
This is where it get interesting. The upstream router is accessed
through the interface re0, so we create a queue of the speed of the
link of that interface, which is 1 Gb/s. pf.conf syntax requires to
use bits per second (b/s or bps) and not bytes per second (Bps or B/s)
which can be misleading.
```pf.conf configuration line
queue std on re0 bandwidth 1G
```
Then, we create a queue that inherits from the parent created before,
this represent the whole upload bandwidth to reach the Internet. We
will make all the traffic reaching the Internet to go through this
queue.
I've set a bandwidth of 900K with a max of 900K, this mean, that this
queue can't let pass more than 900 kilo bits per second (which
represent 900/8 = 112.5 kB/s or kilo Bytes per second). This is the
extreme maximum my Internet access allows me.
```pf.conf configuration line
queue internet parent std bandwidth 900K max 900K
```
The following lines are all sub queues to divide the upload usage, we
want to have a separate queue for DNS request which must not be delayed
to keep responsiveness, but also voip or VPN queues to guarantee a
minimum available for the users.
The web queue is the one which is likely to pass the most data, if you
upload a file through a website, it will pass through the web queue.
The unknown queue is the outgoing traffic that is not known, it's up to
you to put a maximum or not.
Finally, the ackp queue that is split into two other queues, it's the
most important part of the setup.
The "bandwidth xxxK" values should sum up to something around the 900K
defined as a maximum in the parent, this only mean we target to keep
this amount for this queue, this doesn't enforce a minimum or a maximum
which can be defined with min and max keywords.
As explained earlier, you can control the downloading speed by
regulating the sent ACK packets, all ACK will go through the queues
ack_web and ack.
ack_web is a queue dedicated for http/https downloads and the other ack
queue is used for other protocol, I preferred to divide it in two so
other protocol will have a bit more room for themselves to
counterbalance a huge http download (Steam game platform like to make
things hard on this topic by making downloads to simultaneous server
for maximum bandwidth usage).
The two ack queues accumulated can't get over the parent queue set as
406K here. Finding the correct value is empirical, I'll explain later.
All these queues created will allow each queue to guarantee a minimum
from the router point of view, roughly said per protocol here.
Unfortunately, this won't guarantee computers on the network will have
a fair share of the queues! This is a crucial understanding I lacked
at first when trying to do this a few years ago. The solution is to
use the "flow" scheduler by using the flow keyword in the queue, this
will give some slot to every session on the network, guarantying (at
least theoretically) every session have the same time passed to send
data.
I used "flows" only for ACK, it proved to work perfectly fine for me as
it's the most critical part but in fact, it could be applied to every
leaf queues.
```pf.conf configuration line
queue web parent internet bandwidth 220K qlimit 100
queue dns parent internet bandwidth 5K
queue unknown parent internet bandwidth 150K min 100K qlimit 1…
queue vpn parent internet bandwidth 150K min 200K qlimit 1…
queue voip parent internet bandwidth 150K min 150K
queue ping parent internet bandwidth 10K min 10K
queue ackp parent internet bandwidth 200K max 406K
queue ack_web parent ackp bandwidth 200K flows 256
queue ack parent ackp bandwidth 200K flows 256
```
Because packets aren't magically assigned to queues, we need some match
rules for the job. You may notice the notation with parenthesis, this
mean the second member of the parenthesis is the queue dedicated for
ACK packets.
The VOIP queuing is done a bit wide, it seems Microsoft Teams and
Discord VOIP goes through these port ranges, it worked fine from my
experience but may depend of protocols.
```pf.conf configuration line
match proto tcp from em0:network to any queue (unknown,ack)
match proto tcp from em0:network to any port { 80 443 8008 8080 } queue (web,ac…
match proto tcp from em0:network to any port { 53 } queue (dns,ack)
match proto udp from em0:network to any port { 53 } queue dns
# VPN (wireguard, ssh, openvpn)
match proto udp from em0:network to any port { 4443 1194 } queue vpn
match proto tcp from em0:network to any port { 1194 22 } queue (vpn,ack)
# voip (teams)
match proto tcp from em0:network to any port { 3479 50000:50060 } queue voip
match proto udp from em0:network to any port { 3479 50000:50060 } queue voip
# keep some bandwidth for ping packets
match proto icmp from em0:network to any queue ping
```
Simple rule to enable NAT so devices from the LAN network can reach the
Internet.
```pf.conf configuration line
# NAT to the outside
pass out on egress from !(egress:network) nat-to (egress)
```
Default OpenBSD rules that can be kept here.
```pf.conf configuration line
# By default, do not permit remote connections to X11
block return in on ! lo0 proto tcp to port 6000:6010
# Port build user does not need network
block return out log proto {tcp udp} user _pbuild
```
## How to choose values
In the previous section I used absolute values, like 900K or even 406K.
A simple way to define them is to upload a big file to the Internet
and check the upload rate, I use bwm-ng but vnstat or even netstat
(with the correct combination of flags) could work, see your average
bandwidth over 10 or 20 seconds while transferring, and use that value
as a maximum in BITS as a maximum for the internet queue.
As for the ACK queue, it's a bit more tricky and you may tweak it a
lot, this is a balance between full download mode or conservative
download speed. I've lost a bit of download rate for the benefit of
keeping room for more overall responsiveness. Like previously, monitor
your upload rate when you download a big file (or even multiples files
to be sure to fill your download link) and you will see how much will
be used for ACK. It will certainly be a few try and guesses before you
get the perfect value, too low and the maximum download rate will be
reduced, and too high and your link will be filled entirely when
downloading.
## Full configuration
```pf.conf configuration file
set skip on lo
block return # block stateless traffic
pass # establish keep-state
queue std on re0 bandwidth 1G
queue internet parent std bandwidth 900K min 900K max 900K
queue web parent internet bandwidth 220K qlimit 100
queue dns parent internet bandwidth 5K
queue unknown parent internet bandwidth 150K min 100K qlimit 1…
queue vpn parent internet bandwidth 150K min 200K qlimit 100
queue voip parent internet bandwidth 150K min 150K
queue ping parent internet bandwidth 10K min 10K
queue ackp parent internet bandwidth 200K max 406K
queue ack_web parent ackp bandwidth 200K flows 256
queue ack parent ackp bandwidth 200K flows 256
match proto tcp from em0:network to any queue (unknown,ack)
match proto tcp from em0:network to any port { 80 443 8008 8080 } queue (web,ac…
match proto tcp from em0:network to any port { 53 } queue (dns,ack)
match proto udp from em0:network to any port { 53 } queue dns
# VPN (ssh, wireguard, openvpn)
match proto udp from em0:network to any port { 4443 1194 } queue vpn
match proto tcp from em0:network to any port { 1194 22 } queue (vpn,ack)
# voip (teams)
match proto tcp from em0:network to any port { 3479 50000:50060 } queue voip
match proto udp from em0:network to any port { 3479 50000:50060 } queue voip
# ICMP
match proto icmp from em0:network to any queue ping
# NAT
pass out on egress from !(egress:network) nat-to (egress)
# default OpenBSD rules
# By default, do not permit remote connections to X11
block return in on ! lo0 proto tcp to port 6000:6010
# Port build user does not need network
block return out log proto {tcp udp} user _pbuild
```
# How to monitor
There is an excellent tool to monitor the queues in OpenBSD which is
systat in its queue view. Simply call it with "systat queue", you can
define the refresh rate by pressing "s" and a number. If you see
packets being dropped in a queue, you can try to increase the qlimit of
the queue which is the amount of packets kept in the queue and delayed
(it's a FIFO) before dropping them. The default qlimit is 50 and may
be too low.
systat man page anchored to the queues parameter
# Conclusion
I've spent a week scrutinizing pf.conf manual and doing many tests with
many hardware until I understand that ACK were the key and that the
flow queuing mode was what I was looking for. As a result, my network
is much more responsive and still usable even when someone/some device
is using the network without any kind of limit.
The setup can appear a bit complicated but in the end it's only a few
pf.conf lines and using the correct values for your internet access. I
chose to make a lot of queues, but simply separating ack from the
default queue may be enough.
You are viewing proxied material from dataswamp.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.