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.

Overview

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:

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.

Basic installation

  1. 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.

  2. 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/tmp installation is a simple case of:

      $ tar -zxvf alpine-rpi-3.11.6-aarch64.tar.gz -C /mnt/tmp
  3. 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:
  4. Log in as root with no password.

  5. Run setup-alpine to start the configuration process.

  6. Select your keyboard layout from the choices offered.

  7. Select a hostname. I’m going with slownet

  8. 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:

    1. Enter wlan0 at the next prompt
    2. Enter your WiFi network name and password
    3. Select dhcp

    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.

    1. Enter eth0 at the prompt
    2. Enter 192.168.124.1 as the address
    3. Accept the default netmask of 255.255.255.0
    4. No gateway
    5. No manual network configuration
  9. Enter a password for the root account.

  10. Select your timezone or just go with UTC.

  11. If you need a proxy enter it here, otherwise just accept the default none

  12. Accept the default NTP client.

  13. Select a network mirror for packages.

  14. And accept the default SSH server. Wait for the keys to generate — this can take a while on older Pis.

  15. Accept the default config storage location, which should be mmcblk0p1.

  16. Accept the default cache storage directory.

  17. Now you should have a prompt. Try ping 1.1.1.1 to make sure you are actually connected to the Internet.

  18. 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 setup-alpine again.

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.

  1. Install busybox-extras (for udhcpd, the DHCP server we’ll use) and iptables

     apk add busybox-extras iptables
  2. Remove the default DHCP configuration with rm /etc/udhcpd.conf and 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 vim or apk add nano to 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.conf and make it look like this:

    (Change the ranges if you 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 1.1.1.1)

      start           192.168.124.20
      end             192.168.124.254
      interface       eth0
      option  dns     192.168.1.1 1.1.1.1
      option  subnet  255.255.255.0
      option  router  192.168.124.1
      option  domain  local
      option  lease   1800
  3. 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
  4. Edit /etc/sysctl.conf to add the following line, to enable routing:

     net.ipv4.ip_forward = 1
  5. 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.

Traffic control

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 iproute2

apk add iproute2 && lbu commit

The Linux 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 first tc command as a template, substituting a speed as xxxkbit or 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 apply the 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 both 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:

Conclusion

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.