How to make your Internet connection worse
On the face of it it sounds crazy to want to deliberately slow down your Internet connection; if you’ve just had fibre installed at your home or office, why would you want to go back to the Dark Ages? But in fact, it can be a very worthwhile exercise for developers and testers.
Software developers nowadays all too often live in a technology bubble. We have the latest phones and newest laptops, connected to multiple 4k screens and reliable, high-bandwidth network connections. A common trap to fall into is over-fitting the design of software we work on to our own environment, forgetting that the users we’re building for might have very different equipment. I’ve worked on projects in the past that looked absolutely beautiful when viewed on a 15″ MacBook Pro or on one of the standard-issue external displays in the team office. If you viewed the site on anything else — from a tablet to 34″ ultrawide — well, things got ugly and the glitches started piling up.
It’s important to recognise that this happens automatically unless a deliberate effort is made to counter the effect and consciously consider a wide range of hardware environments; which is one reason I keep a selection of older monitors more representative of ‘average’ use around the office in addition to my primary display.
This is all well and good for the visual side of a project; what about the network? Exactly the same sort of problems will arise if you develop in an office with multi-hundred-megabit fibre and completely forget about the network as a constraint.
Deliberately limiting your Internet connection to something more representative of your target market is a crucial part of ensuring you provide the best possible user experience. Modern browsers do provide some pre-packaged throttling options in the developer console, which are a great start, but they don’t provide any flexibility, and don’t help at all if you’re working on mobile or on desktop applications.
Fortunately it’s surprisingly straightforward to build a simple, configurable network rate limiter. There are many different approaches we could take, covering a wide range of different hardware requirements, complexity, automation, and sophistication of the end product. For this article though, we’ll keep it as simple as possible.
In the name of simplicity we’re going to base our build on a Raspberry Pi; any model that supports both WiFi and Ethernet will do. Instead of the usual Raspbian we’ll base our build on Alpine Linux; we don’t really need to run any software except for the base Linux installation, and Alpine is a very lightweight distribution that has the advantage of running entirely from RAM once it’s booted. This means we’ll end up with something closer to a network application than a “computer”; a device that you can just power on and plug into when you want slow Internet, and when you’re done you can just kill the power without worrying about it.
We’ll split the build into three sections:
- Installing and configuring Alpine
- Setting up NAT
- Configuring network limits.
Although it looks like there are a lot of steps below, I promise it’s not that bad! It’s mostly just stepping through the Alpine setup tool.
Assuming you have your Raspberry Pi and SD card in hand and ready to go, head over to the Alpine Linux download page and grab the appropriate file.
Raspberry Pi archives are at the bottom. I’m using aarch64 with my RPi 4; if you’re using an earlier board you might need the armhf version instead. See the wiki for a breakdown of which builds match which boards.
Installing Alpine is not the same process of writing an image to an SD card that you might be used to from Raspbian or other distros. There is no “image” as such; all you need to do is unpack the archive you downloaded directly into the root of a FAT32-formatted SD card.
Most cards already come formatted appropriately, so if you have mounted the card at
/mnt/tmpinstallation is a simple case of:
$ tar -zxvf alpine-rpi-3.11.6-aarch64.tar.gz -C /mnt/tmp
Next, put the card into your Pi and boot it with a monitor and keyboard attached. If the Pi doesn’t boot but just hangs with a multicoloured rainbow square on the screen the chances are you downloaded the wrong archive; refer to the Alpine wiki linked above and try again.
If everything worked, you should see:
Welcome to Alpine Linux 3.11 Kernel 5.4.34-0-rpi4 on an aarch64 (/dev/tty1) localhost login:
Log in as
rootwith no password.
setup-alpineto start the configuration process.
Select your keyboard layout from the choices offered.
Select a hostname. I’m going with
Now we come to the network configuration.
The idea here is to have traffic from the computer you want to limit flow in one network interface and out the other to your primary network and then to the Internet, giving us the chance to “influence” it en route. For the purposes of this walkthrough we’ll assume the test machine is going to plug in via Ethernet, and we’ll use WiFi as the upstream connection out to the rest of the world.
Start off by configuring the WiFi connection:
wlan0at the next prompt
- Enter your WiFi network name and password
That should be enough to give the Pi an Internet connection. Now we’ll configure the test network. I’m using 192.168.124.1/24 below, but in the unlikley event that that happens to conflict with your main network you’ll need to pick another private address range.
eth0at the prompt
192.168.124.1as the address
- Accept the default netmask of
- No gateway
- No manual network configuration
Enter a password for the root account.
Select your timezone or just go with UTC.
If you need a proxy enter it here, otherwise just accept the default
Accept the default NTP client.
Select a network mirror for packages.
And accept the default SSH server. Wait for the keys to generate — this can take a while on older Pis.
Accept the default config storage location, which should be
Accept the default cache storage directory.
Now you should have a prompt. Try
ping 184.108.40.206to make sure you are actually connected to the Internet.
Configure the wireless network to start properly on boot by running
rc-update add wpa_supplicant boot
The final stage of this initial setup is to save all our configuration changes to disk and reboot. Recall that one of the advantages of using Alpine Linux for this project is that it runs completely from RAM; this is exactly what we want when everything’s up and running properly, but it means that if we rebooted now all our changes would be lost.
To persist the changes we just made to disk so that everything is correctly configured next time we boot, just run
$ lbu commit
Now for the moment of truth; reboot the Pi with
reboot and make sure it
starts correctly, joins your WiFi network, and can still ping out to the
Internet. If not, power-cycle the Pi and try running through
Assuming everything is OK, we’re ready for phase two!
Set up NAT
By this point we have a Pi with two networks; an upstream connection to your wireless network along with its own local Ethernet network on 192.168.124.0/24 — but they don’t talk. The next step is to set up a basic NAT configuration so that machines connected to the Ethernet network can connect through the Pi to the outside world. We’re not going to worry about rate limiting in this section; we just want a connection.
For this to work we need a DHCP server on the Ethernet interface so we can offer clients IP addresses, and we need some forwarding and NAT rules to accept traffic from them and pass it to the outside world.
Install busybox-extras (for udhcpd, the DHCP server we’ll use) and iptables
apk add busybox-extras iptables
Remove the default DHCP configuration with
rm /etc/udhcpd.confand make a new one.
By default Alpine ships with the Busybox version of vi; if you’d prefer to use a different editor you can do
apk add vimor
apk add nanoto install your preference. If you’re not sure, go for nano.
Once you’ve got an editor you’re happy with, edit the configuration file with
nano /etc/udhcpd.confand make it look like this:
(Change the ranges if picked something other than 192.168.124.0 above. I’ve used 192.168.1.1 as a place-holder for your main network DNS server; fill this in to match your network, or drop it and rely just on 220.127.116.11)
start 192.168.124.20 end 192.168.124.254 interface eth0 option dns 192.168.1.1 18.104.22.168 option subnet 255.255.255.0 option router 192.168.124.1 option domain local option lease 1800
Next set up iptables to NAT from Ethernet to WiFi with the following three commands, entered at the prompt:
$ iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE $ iptables -A FORWARD -i wlan0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT $ iptables -A FORWARD -i eth0 -o wlan0 -j ACCEPT
/etc/sysctl.confto add the following line, to enable routing:
net.ipv4.ip_forward = 1
As with the basic setup, the final step is save our changes, configure everything to be restarted on boot, and reboot the Pi.
service iptables save rc-update add udhcpd rc-update add sysctl rc-update add iptables lbu commit reboot
When the Pi has finished rebooting you should be able to plug a laptop into its Ethernet port and see that it receives an IP address between 192.168.124.20 and .254, and you should be able to browse the Internet from the test machine through the Pi.
After all that setup to get the Pi installed and the networking configured, it’s now time to configure the rate-limiting we want. This section is tiny by comparison, but it’s where all the magic happens!
First we need to install another package to get the
tc traffic control tool,
which is bundled in with
apk add iproute2 && lbu commit
tc command allows
us to manipulate network traffic as it passes through the system. It’s
extremely powerful and flexible, and correspondingly quite confusing to get
started with. Fortunately we only need a small subset: the
netem facility, which
allows us to emulate network devices.
To start, perform a speed test from your test laptop to see what kind of speeds you’re currently getting (Google “internet speed test” to use Google’s version, or hit fast.com. Note that it might already be a bit slower than you’re used to due to the limitations of the Pi’s Ethernet)
Now it’s time for some nostalgia. Years ago I spent 18 months in Antarctica on the wrong end of a VSAT satellite Internet connection, with very limited bandwidth and high latency. Let’s recreate that:
tc qdisc add dev eth0 root netem rate 768kbit delay 600ms
This emulates a 768kbit connection, with 600ms of added latency for the satellite round trip time. Try the speedtest again and see the difference! Be aware that some speed testers have difficult with really low speeds, but it should still be enough to get a rough idea. (To fully recreate the Antarctic experience, try sharing this link with 100 friends — all on Facebook — and plenty of automated mail, weather, and scientific monitoring services.)
Now… how about a modem? Remove the current limits with
$ tc qdisc del dev eth0 root netem
and then try this:
$ tc qdisc add dev eth0 root netem rate 33kbit
Welcome back to the days of dial-up!
Feel free to play around with different settings for a while — test out your
most recent website on a 3Mbit connection and see how it fares. Just use the
tc command as a template, substituting a speed as
yyymbit and an optional delay.
You can use your new speed limiter just like this, booting it up and entering
your preferred speed each time depending on your needs. Alternatively, if you
settle on a single testing profile and just want to use it every time, run
nano /etc/network/interfaces to edit the network configuration.
Add the following line at the bottom, after
eth0’s netmask configuration to
tc limits on boot; just fill in the rate and delay you want.
post-up tc qdisc add dev eth0 root netem rate 1mbit delay 100ms
Limits and extensions
We haven’t even scratched the surface of
tc’s capabilities. One of the
limitations of the setup described above is that all the limits are applied on
the download part of the link; it would be more accurate to apply
tc rules to
eth0 for download and
wlan0 for upload, splitting the desired
latency between the two interfaces. That can make quite a difference on
high-latency links like satellites, but what we’ve got so far is plenty to get
started with, and provides an easy-to-recreate basis for future experimentation.
Some things you might want to consider trying:
Invert the networking so that the Pi uses Ethernet for its uplink and broadcasts its own slow WiFi network (you’ll need to throw
hostapdinto the mix for this), making it easy to test mobile devices.
Use a dedicated network board like a PC Engines APU2 with multiple Ethernet ports instead of a Pi. You could have an uplink port and several downlink ports with different speed limits, or use one of the extras as a mirror port so you can see what’s passing over the network.
Make a little menu script so you can log in over SSH and pick the limits you want from a preconfigured list.
Read up on more advanced
tcoptions for more accurate emulation of things like 3G / LTE connections.
I hope this is a useful tool that helps make the Internet useful for everyone, not just those who live in well-connected areas and tech hubs. It’s easy to assume everyone has unlimited, high-quality bandwidth these days, but it’s simply not the case — and you don’t have to go as far as you might think to find places where bandwidth becomes a limitation.
I hope you found the article useful!
Please let you know what you enjoyed, how you think it could have been better, or what kind of articles you’d like to see in the future by dropping me a note in the box below.
If you’d like a response don’t forget to include a way for me to get in touch!