Skip to main content

Building a router with NixOS

Β· 26 min read

I've now been using NixOS for one year. I use it to manage both my private PC and my laptop at work. Overall, it has been a great experience. I really appreciate the scale and control it brings to my digital life.

In May 2022, I stepped it up a notch by adding a third device running NixOS, namely a custom-built router! After a month of interspersed work I had successfully replaced my home network router.

In this blog post I'll go through why I embarked on this journey, how I did it, and lessons learned.

Why? πŸ€”#

I've had sporadic issues with my previous router (Asus N56U). It worked fine most of the time, but when it didn't it was bad:

  • network speeds dropping off a cliff for a few minutes
  • connectivity dying completely for a few seconds

For most activities this wasn't a deal breaker, but it was a huge pain not being able to take important meetings at home and be confident that I'd have a good connection. Routers should just workβ„’ and this one didn't.

Selecting a new router#

Earlier in the year, a friend was asking for shopping advice for a new router. We quickly agreed that regular consumer tests can't be relied on because they don't test network stability over time, but instead focus on measuring speed and latency. I couldn't give a recommendation, aside from referring to hardware that could be flashed with open source router software.

A few weeks or months after our conversation, I found out about PC Engines APU2. An x86 router with open source hardware that can be flashed with your practically any Linux distribution. It seemed like a good solution to stability concerns: if you have complete control over your router, you can "just" fix it if something's not working reliably.

With NixOS in mind and a will to learn more about home networking, I was convinced that this would be a good choice. I ordered a unit from TekLager (not affiliated) with the following configuration:

Some major selling points that made me decide that this router actually was a good choice:

Hardware was assembled by the folks at TekLager, neatly packaged, and delivered within a few days. Then followed an intense, interspersed battle of configuration until I finally got it working at the end of May. As I'm writing this blog post, my home network is fully supported by this little beast!

Why NixOS?#

There are a number of distributions that are designed to be run on routers. Why would I use NixOS instead of pfSense, DD-WRT, or OpenWRT?

If you are experienced with Nix(OS), it should come as no surprise to you that I want all my devices to use it. But if you're new to NixOS or haven't been sold on its benefits, I'd say that it boils down to three things:

Maintainability#

Since I'm already using NixOS to configure my work & private PCs, the additional overhead of a new device is pretty small. This is mainly because I'm using a single Git repository to maintain all devices, so reusability is very high.

Package updates are easy, requiring only one or two commands. And updating to new releases is also relatively painless, just requiring a one-line change to bump the nixpkgs release. Furthermore, I will likely get these updates for free, since I'm already declaring them for my other devices.

Customizability#

The router can be configured exactly as I want it to be. It will be running a subset of the same software with the same configurations as I'm used to having on my PCs. It will have Neovim and tmux, a range of networking tools, and dotfiles, all of them configured like I'm used to - with practically zero overhead.

Learning new things#

I'm still a Nix newbie, so this is a good project to grow that skillset. And in particular, by forcing myself to declaritively design my home network I'll have a good opportunity to learn more about networking!

Why not a normal router?#

Where's the fun in that? :)

Timeline πŸ•’#

In real time it took about a month to get everything working, but in total I think I've spent less than 25 hours. I did most work before I had set up ActivityWatch, so I don't have any reliable data to go on. Most things were very easy to set up, but WiFi took a long time to get right.

The most time consuming task in the project though has definitely been writing this blog post (not included in the 25h)!

May 3: Ordered the package#

May 6: Picked up the delivery#

May 7: Installed NixOS and got the basic configs up and running#

This was done over serial with a USB install. There were no surprises here if you're familiar with using serial.

For more detailed instructions on this piece, these references might help:

May 8: Basic MVP for wireless & wired working#

Getting an initial wired connection was fairly straight forward, although I didn't add any features except being able to forward traffic and to accept SSH. With that of the way, I turned my attention to WiFi which was significantly harder to configure.

After a lot of struggling, I managed to get hostapd launching without errors, but to my surprise I was only getting ~10Mbit/s. Much lower than the benchmark done by TekLager at 200Mbit/s.

Since TekLager had achieved 200Mbit/s on OpenWRT, I thought it would be a good test to see if I could too. I flashed the board with OpenWRT instead and after a few minutes of basic setup in their web UI, I confirmed the ~200Mbit/s number. Evidently, the low speeds I had on NixOS was not hardware related.

K900 on the NixOS Matrix channel informed me that OpenWRT has a lot of patches on top of hostapd, as well as the wireless driver, which could be the differing factor.

Determined to fix this, I set out to find out how I could use the same patches that were used in OpenWRT while still using NixOS.

May 11, 14: Learned how to override Nix package attributes#

This took quite a while to wrap my head around. As they say, Nix is super easy 95% of the time, but in the remaining 5% it's terribly difficult to control.

Having learnt how overlays work, I successfully managed to apply OpenWRT's patches on hostapd. Some of them didn't apply cleanly, such as the ones bringing in support for dbus which is an IPC software used in OpenWRT, so I ignored those.

With the patched version of hostapd running, I was then able to take the configuration that OpenWRT set up and reduce it until there were no superfluous or erroneous options left. And taking new measurements with iperf, I was able to get the same speeds as on OpenWRT! However, as explained in the entry for June 12 below, the patches were not what made the network speeds go up.

More details on how I added the patches can be found in this section.

May 14: WiFi network up and running, but DHCP wasn't working.#

Looking at systemd logs on both the router and my PC, I could see that the WiFi protocol was working as intended. Logs looked something like this:

workstation kernel: wlp39s0: authenticate with 04:f0:21:ac:39:faworkstation kernel: wlp39s0: send auth to 04:f0:21:ac:39:fa (try 1/3)workstation kernel: wlp39s0: authenticatedworkstation kernel: wlp39s0: associate with 04:f0:21:ac:39:fa (try 1/3)workstation kernel: wlp39s0: RX AssocResp from 04:f0:21:ac:39:fa (capab=0x411 status=0 aid=3)workstation kernel: wlp39s0: associated

But after being authenticated, clients would not receive an IP address. I didn't find out why and other things in life took precedent, so the project got benched for a while.

May 29: Fix the DHCP issue#

Two weeks later, I took the project up again and found the issue immediately. The firewall was blocking clients from negotiating an IP over DHCP. Fixing that was easy, and from this point on it was mostly smooth sailing.

May 30: All vital features working!#

After a lot of system rebuilds and experimentation, I finally got all vital features working. This was the day when I replaced my old router. Since then, the APU2 has been powering my home network.

May 31: Add static routes with DnsMasq#

Since I have a few devices that I want to be able to SSH to or mount files from/to, it makes things much easier if I can guarantee that they'll have the same IP. This was a simple one-line change per device to DnsMasq's config.

June 2: Add Grafana monitoring#

I've been using Grafana at work a lot for monitoring & alerts, and it seemed like a great fit to monitor my home network health. Xe has an excellent blog post from 2020 on how to set it up on NixOS, and getting the bare minimum up and running is ridiculously simple:

{ config, pkgs, ... }:{  # https://xeiaso.net/blog/prometheus-grafana-loki-nixos-2020-11-20  services.grafana = {    enable = true;    port = 8888;    addr = "0.0.0.0";    dataDir = "/var/lib/grafana";  };
  services.prometheus = {    enable = true;    port = 9990;    exporters = {      node = {        enable = true;        enabledCollectors = [ "systemd" ];        port = 9991;      };    };    scrapeConfigs = [      {        job_name = "chrysalis";        static_configs = [{          targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.node.port}" ];        }];      }    ];  };}

These 30 lines do three things:

  1. Enables Grafana to run as a service and spins up its Web UI at 0.0.0.0:8888 (accessible to any network interface, i.e. any connected client as long as it's allowed by the firewall).
  2. Enables Prometheus to collect Systemd metrics.
  3. Makes Prometheus available as a data source running on 127.0.0.1:9991 (only accessible on the loopback interface, i.e. only by the router itself).

Note that this opens up Grafana to the public as well, since traffic from enp1s0 is also able to reach 0.0.0.0. However, it's not a problem because traffic to port 8888 from enp1s0 will be blocked by the firewall.

June 5: Revert from 5GHz WiFi to 2.4Ghz, since our robot vacuum cleaner didn't support 5GHz.#

When trying to clean our apartment, we found out that our RoboRock S5 Max didn't support 5GHz WiFi. Since my NIC can only support 2.4 or 5GHz at any given time, I therefore had to regress to 2.4Ghz.

This actually turned out to be a good thing though, since I didn't need the extra speed that 5GHz offers and the range with 2.4GHz is much better.

June 12: Publish this blog post & verify hostapd patches#

To really find out what made WiFi speeds jump from 10Mbit/s to 200, I removed the OpenWRT patches and rebuilt the system to see if that was the differing factor. Using iperf, I found no significant difference between patched and non-patched builds. This means that it was not the patches from OpenWRT that made the WiFi go faster, but rather config changes I made to Hostapd at the same time. Exactly what those changes were, I don't know - and I don't have the interest to pursue that rabbit hole.

June 25: Add WireGuard#

I had heard about WireGuard from a colleague a few years back, but I had no real conceptions of how it worked besides that it was some kind of improvement over OpenVPN. Understatement of the year! More information on the setup and experience TBD.

June 25: Improve firewall#

In my first configuration I had a rather simplistic firewall: trusted interfaces and untrusted interfaces. I did not distinguish between outbound and inbound connections for the untrusted interfaces, meaning that the ports I opened for the guest network were also opened for inbound traffic from "the outside".

To fix this, I had to dig through a few Github issues to find the correct syntax. The solution was to specify allowed TCP/UDP ports per interface instead of globally. See more in the Firewall section below.

The code πŸ‘¨β€πŸ’»#

My full configuration of devices is available on Github.

For all of my current devices, I am currently maintaining 17K lines of Nix code. If you ignore auto-generated code, we're instead looking at 2K lines.

Of those 2K lines, about 500 are specific to my router's configuration.

If you want to explore the code on your own, here are some tips:

  • The top level configurations of all devices are listed under nixosConfigurations in flake.nix .
  • hosts/router.nix contains the hardware specific configurations for the router.
  • sys/router.nix configures generic network things like subnets, bridges, and DHCP
  • sys/hostapd.nix configures the WiFi networks
  • sys/monitoring.nix configures Prometheus and Grafana (used to monitor system & network health)

Overview πŸ—ΊοΈ#

Schematic#

This is a very high level sketch of how the network is set up.

         ISP          |      Ethernet (enp1s0)          |       ------------------       |      APU2      |       ------------------        |              |   WiFi (wlp3s0)     bridge (br0)        |       -------|     --------   |      |     |      |   |      |  guest   private      |                       |                 Ethernet (enp2s0)                       |                    Wired LAN

The guest network is set up with:

  • Separate WiFi network configured with hostapd
  • Client isolation (hostapd setting)
  • Its own virtual NIC
  • Its own subnet
  • Only a basic set of ports are open

The private network is set up with:

  • All ports allowed
  • A bridge between wired and wireless clients, enabling them to communicate with each other

In practice, this means that

  • Guests can only do basic web browsing / emails / git
  • Guests can't ssh to the router, even if they have the correct key
  • Guests can't reach other clients on the same network nor on the private network
  • Guests can't reach any service hosted on the router, such as Grafana, because it'll be blocked by the firewall
  • Since guests don't have access to anything besides "the internet", I don't have to care about who I give my WiFi password to

Reality#

This is how it looks in real life. I've got two permanent units connected over wire: a NAS and a Raspberry Pi (the third wire connected to the switch is temporary). The rest of all my devices are connected over WiFi.

Picture of the router setup

Things I've learned πŸ€“#

I've had a vague idea about most of these topics, but I haven't fully understood any of them. I still don't understand the details of many of these things, mainly because NixOS makes it so easy to configure :) But still, I have learned a lot!

IP forwarding#

To quote an answer to the Stack Overflow question "IP Forwarding = when and why is this required?":

IP forwarding should be enabled when you want the system to act as a router, that is transfer IP packets from one network to another.

This will allow packets to flow between different network interfaces. Unless enabled, these packets would be dropped by the kernel. Having this enabled is what allows us to communicate with the outside world.

  boot = {    kernel = {      sysctl = {        "net.ipv4.conf.all.forwarding" = true;        "net.ipv6.conf.all.forwarding" = true;      };    };  };

NAT#

NAT is an abbreviation of Network address translation. The router is the only device that is public facing. Any packets going to clients connected to the router will be addressed to the router. It is then the router's responsibility to forward the packet to correct client.

In NixOS, we can configure it like so:

  networking.nat = {    enable = true;    internalInterfaces = [      "br0"      "wguest"    ];    externalInterface = "enp1s0";  };

This means that any traffic coming from the enp1s0 interface to br0 or wguest will need to translate the destination IP with NAT. Likewise, for any traffic from br0 or wguest the source IP will be replaced with the router's external IP.

For more information, see Wikipedia. The image in their article about NAT is great to get an intuitive understanding of what's happening.

Network interfaces#

A (virtual) network interface (as shown in e.g. ip link) is an abstraction that can correspond directly to a network interface controller (NIC), but it doesn't have to. An example of a real use case of these abstracted interfaces, which we will see later on, is to separate a NIC into multiple virtual interfaces and then apply different traffic rules on those abstractions. So they use the same hardware, but their traffic is treated differently.

Network bridging#

In order to enable communication between wired and wireless clients, we need a network bridge. A network bridge will allow traffic from two interfaces to act as if come from the same interface.

With NixOS, we configure this like so:

  networking.bridges = {    br0 = {      interfaces = [        "enp2s0"        "wlp3s0"      ];    };  };  networking.interfaces = {    br0 = {      useDHCP = false;      ipv4.addresses = [        {          address = "192.168.1.1";          prefixLength = 24;        }      ];    };  };

Note that we specify a subnet IP block for the bridge and not individually for the network interfaces as devices on those will be assigned IPs from the bridge's IP block.

DHCP#

DHCP is a client-server protocol, where clients are given an IP address by a server. We'll need to use the router as a DHCP server in order for our clients to receive an IP address. Clients need to have an IP address in order to receive data (how can the mailman deliver your mail if he doesn't know your address?).

This protocol is also used between the router and ISP, in order for the router to receive an external IP address.

A fun detail I discovered when I hadn't gotten DHCP to work yet, was that there is a reserved block of IP addresses used when no IP address is specified (by static assignment or DHCP): 169.254.0.0/16.

Which interfaces should the router act as a server on?#

In NixOS, the config option networking.useDHCP and networking.interfaces.x.useDHCP configure whether the device should request an IP as a DHCP client. This makes sense as most devices aren't routers, but for a router we want to make some special configurations here.

For the physical network interfaces, we configure DHCP like so:

  networking.useDHCP = false; # Disable it by default  networking.interfaces.enp1s0.useDHCP = true; # Request an IP from the ISP  networking.interfaces.enp2s0.useDHCP = true; # Request an IP on the local wired interface  networking.interfaces.wlp3s0.useDHCP = true; # Request an IP on the wireless interface

Now, enabling it for enp1s0 makes sense because we need to be assigned an external IP by the internet service provider. But why are we acting as a DHCP client on the local interfaces enp2s0 and wlp3s0?

If you want to configure the network based on the physical interfaces directly, you wouldn't. But since I've configured my network with a bridge and a virtual interface for the guest network, we enable DHCP on the physical interfaces so that they are assigned IPs from the virtual interfaces. [note: I'm speculating here, take it with a grain of salt and please LMK if it is wrong!]

DnsMasq#

A service responsible for DNS and DHCP. A very powerful little tool!

With this small config, we've got everything we need.

  • domain-needed makes sure that we ignore DNS requests that don't have a dot or domain specified. I'm not sure what threat this prevents, but why not!
  • The list of interface are matched in order with the list of dhcp-range, so the network bridge br0 is assigned the IP block 192.168.1.10-192.168.1.254 and wguest is assigned 192.168.2.10-192.168.2.254.
  • The 24h specified at the end of dhcp-range is the lease time in which to cache a client's IP address. So if a client reconnects within that time range, it will be assigned the same IP.
  • The dhcp-host entries specify static IP addresses for a couple of devices on my network
  services.dnsmasq = {    enable = true;    servers = [ "9.9.9.9" "1.1.1.1" ];    extraConfig = ''      domain-needed      interface=br0      interface=wguest      dhcp-range=192.168.1.10,192.168.1.254,24h      dhcp-range=192.168.2.10,192.168.2.254,24h      # kodi      dhcp-host=b8:27:eb:84:09:f8,192.168.1.90      # NAS      dhcp-host=00:11:32:33:30:5b,192.168.1.65      # workstation      dhcp-host=30:9c:23:1b:a5:4d,192.168.1.83
    '';  };

Hostapd#

Described in its man-page as "A user spaces daemon for access point and authentication servers". Super hard to configure properly.

Here are some vital references I've found:

I feel more as if I've learnt how to configure Hostapd than how WiFi networks work in general. But maybe that's not a bad thing.

To see my full Hostapd configuration, have a look at my Github.

Overriding nix package attributes#

To add the patches from OpenWRT, I had to override the patches attribute of the hostapd package by using a feature in Nixpkgs called overlays.

The end result looked something like this:

  nixpkgs.overlays = [    (self: super: {      hostapd = super.hostapd.overrideAttrs (old: rec {        patches = [          (builtins.fetchurl {            url = "https://raw.githubusercontent.com/openwrt/openwrt/master/package/network/services/hostapd/patches/001-wolfssl-init-RNG-with-ECC-key.patch";            sha256 = "1h4wqn6dpc5vw19428v6s49i3xsdqc1ikwv6gvdhs2ly98cxwb91";          })          ...        ]      })    })  ];

In addition to adding the patches, I made sure to use Hostapd 2.10 instead of 2.9 and edited the unpackPhase to include files added by OpenWRT that were referenced but not included in their patches.

However, as explained previously, I later discovered that the patches made no noticable difference in terms of network speed. So I removed them. I still use an overlay to use version 2.10 instead though.

Firewall#

NixOS has a firewall module with which you can neatly specify a lot of rules. I thought it would make sense to be strict about what clients on the guest network should be able to do, but not restrict traffic on my private network.

A simple configuration could look something like this:

  networking.firewall = {    enable = true;    trustedInterfaces = [ ];    allowedTCPPorts = [    ];    allowedUDPPorts = [    ];  };

However, there are two issues with this.

Inbound vs outbound#

Tha allowedTCPPorts and allowedUDPPorts attributes specify allowed ports for all interfaces. If we open up 2222 for TCP, this also also opens up the same port for the outwards facing interface.

To fix this, we shold specify allowed ports per interface instead of globally:

  networking.firewall = {    enable = true;    trustedInterfaces = [ "br0" "wg0" ];
    interfaces = {      enp1s0 = {        allowedTCPPorts = [ ];        allowedUDPPorts = [          # Wireguard          666        ];      };      # https://serverfault.com/a/424226      wguest = {        allowedTCPPorts = [          # DNS          53          # HTTP(S)          80          443          110          # Email (pop3, pop3s)          995          114          # Email (imap, imaps)          993          # Email (SMTP Submission RFC 6409)          587          # Git          2222        ];        allowedUDPPorts = [          # https://serverfault.com/a/424226          # DNS          53          # DHCP          67          68          # NTP          123        ];      };    };  };

SSH & Port 22#

With the above firewall rules, clients on an untrusted interfaces will still be able to SSH to the router (if they have my key), even if port 22 isn't explicitly opened. This is because the ssh service in NixOS will open the firewall unless we explicitly tell it not to:

  # Prevent sshd from opening port 22 (circumventing the firewall)  services.openssh.openFirewall = false;

Stability 🀹#

I don't have good enough monitoring in place yet to give a factual report about stability. When I do, I'll come back with data and my experience on how things have gone!

Speed πŸš€#

With Ethernet, direct:#

$ iperf -c 192.168.1.1Connecting to host 192.168.1.1, port 5201[  5] local 192.168.1.43 port 43824 connected to 192.168.1.1 port 5201[ ID] Interval           Transfer     Bitrate         Retr  Cwnd[  5]   0.00-1.00   sec   114 MBytes   953 Mbits/sec    0    375 KBytes[  5]   1.00-2.00   sec   113 MBytes   945 Mbits/sec    0    375 KBytes[  5]   2.00-3.00   sec   112 MBytes   942 Mbits/sec    0    570 KBytes[  5]   3.00-4.00   sec   113 MBytes   946 Mbits/sec    0    570 KBytes[  5]   4.00-5.00   sec   112 MBytes   937 Mbits/sec    0    570 KBytes[  5]   5.00-6.00   sec   112 MBytes   937 Mbits/sec    0    570 KBytes[  5]   6.00-7.00   sec   113 MBytes   949 Mbits/sec    0    570 KBytes[  5]   7.00-8.00   sec   112 MBytes   939 Mbits/sec    0    570 KBytes[  5]   8.00-9.00   sec   112 MBytes   938 Mbits/sec    0    570 KBytes[  5]   9.00-10.00  sec   113 MBytes   945 Mbits/sec    0    570 KBytes- - - - - - - - - - - - - - - - - - - - - - - - -[ ID] Interval           Transfer     Bitrate         Retr[  5]   0.00-10.00  sec  1.10 GBytes   943 Mbits/sec    0             sender[  5]   0.00-10.00  sec  1.10 GBytes   941 Mbits/sec                  receiver
iperf Done.

With Ethernet, via switch:#

The switch is a cheap thing I picked up a few years ago, capped at 100Mbit/s. Until I replace it with a gigabit switch, this is the maximum speed with which my wired clients can communicate.

$ iperf -c 192.168.1.1Connecting to host 192.168.1.1, port 5201[  5] local 192.168.1.43 port 43938 connected to 192.168.1.1 port 5201[ ID] Interval           Transfer     Bitrate         Retr  Cwnd[  5]   0.00-1.00   sec  11.8 MBytes  99.0 Mbits/sec    0    139 KBytes[  5]   1.00-2.00   sec  11.3 MBytes  94.6 Mbits/sec    0    139 KBytes[  5]   2.00-3.00   sec  11.3 MBytes  94.6 Mbits/sec    0    146 KBytes[  5]   3.00-4.00   sec  11.3 MBytes  94.6 Mbits/sec    0    146 KBytes[  5]   4.00-5.00   sec  11.3 MBytes  94.6 Mbits/sec    0    146 KBytes[  5]   5.00-6.00   sec  11.0 MBytes  92.2 Mbits/sec    0    146 KBytes[  5]   6.00-7.00   sec  11.3 MBytes  94.6 Mbits/sec    0    146 KBytes[  5]   7.00-8.00   sec  11.3 MBytes  94.6 Mbits/sec    0    146 KBytes[  5]   8.00-9.00   sec  11.3 MBytes  94.6 Mbits/sec    0    146 KBytes[  5]   9.00-10.00  sec  11.3 MBytes  95.1 Mbits/sec    0    216 KBytes- - - - - - - - - - - - - - - - - - - - - - - - -[ ID] Interval           Transfer     Bitrate         Retr[  5]   0.00-10.00  sec   113 MBytes  94.9 Mbits/sec    0             sender[  5]   0.00-10.00  sec   112 MBytes  94.1 Mbits/sec                  receiver
iperf Done.johanan@workstation (master) ~/os

With 5GHz WiFi:#

$ iperf -c 192.168.1.1Connecting to host 192.168.1.1, port 5201[  5] local 192.168.1.83 port 43332 connected to 192.168.1.1 port 5201[ ID] Interval           Transfer     Bitrate         Retr  Cwnd[  5]   0.00-1.00   sec  22.9 MBytes   192 Mbits/sec    0    918 KBytes[  5]   1.00-2.00   sec  21.2 MBytes   178 Mbits/sec    0   1.76 MBytes[  5]   2.00-3.00   sec  22.5 MBytes   189 Mbits/sec    0   2.78 MBytes[  5]   3.00-4.00   sec  22.5 MBytes   189 Mbits/sec    0   3.00 MBytes[  5]   4.00-5.00   sec  22.5 MBytes   189 Mbits/sec    0   3.00 MBytes[  5]   5.00-6.00   sec  25.0 MBytes   210 Mbits/sec    0   3.00 MBytes[  5]   6.00-7.00   sec  22.5 MBytes   189 Mbits/sec    0   3.00 MBytes[  5]   7.00-8.00   sec  22.5 MBytes   189 Mbits/sec    0   3.00 MBytes[  5]   8.00-9.00   sec  22.5 MBytes   189 Mbits/sec    0   3.00 MBytes[  5]   9.00-10.00  sec  23.8 MBytes   199 Mbits/sec    0   3.00 MBytes- - - - - - - - - - - - - - - - - - - - - - - - -[ ID] Interval           Transfer     Bitrate         Retr[  5]   0.00-10.00  sec   228 MBytes   191 Mbits/sec    0             sender[  5]   0.00-10.01  sec   227 MBytes   191 Mbits/sec                  receiver
iperf Done.

With 2.4GHz WiFi:#

Theoretically, 2.4GHz should be able to support faster speeds than this. There might be a few improvements that can be made here with hostapd, but I will most likely not experiment with it since I don't really need a faster connection than this.

$ iperf -c 192.168.1.1Connecting to host 192.168.1.1, port 5201[  5] local 192.168.1.83 port 55626 connected to 192.168.1.1 port 5201[ ID] Interval           Transfer     Bitrate         Retr  Cwnd[  5]   0.00-1.00   sec  11.2 MBytes  94.3 Mbits/sec    0    370 KBytes[  5]   1.00-2.00   sec  8.64 MBytes  72.5 Mbits/sec    0    386 KBytes[  5]   2.00-3.00   sec  9.69 MBytes  81.3 Mbits/sec    0    469 KBytes[  5]   3.00-4.00   sec  8.70 MBytes  73.0 Mbits/sec    0    491 KBytes[  5]   4.00-5.00   sec  10.3 MBytes  86.5 Mbits/sec    0    513 KBytes[  5]   5.00-6.00   sec  8.29 MBytes  69.5 Mbits/sec    0    563 KBytes[  5]   6.00-7.00   sec  8.05 MBytes  67.6 Mbits/sec    0    567 KBytes[  5]   7.00-8.00   sec  6.77 MBytes  56.8 Mbits/sec    0    567 KBytes[  5]   8.00-9.00   sec  8.02 MBytes  67.2 Mbits/sec    0    567 KBytes[  5]   9.00-10.00  sec  10.3 MBytes  86.3 Mbits/sec    0    597 KBytes- - - - - - - - - - - - - - - - - - - - - - - - -[ ID] Interval           Transfer     Bitrate         Retr[  5]   0.00-10.00  sec  90.0 MBytes  75.5 Mbits/sec    0             sender[  5]   0.00-10.01  sec  86.8 MBytes  72.7 Mbits/sec                  receiver
iperf Done.

Was it worth it? πŸ’Έβ³#

Was it worth the time?#

I think so! It's been a very fun and rewarding project. Most side projects I work on tend to get discontinued after a while, but since this has a huge impact on my daily life it has been very motivating to work on!

Was it worth the money?#

For me, yes. There are surely tons of cheaper and better routers out there. But with a router completely under my control, with a distribution that I can work efficiently with, I think I've got a much greater chance to guarantee network stability than what I would have with an off-the-shelf router.

It's also an investment in my skills, with a very good potential ROI as I'm still very early in my career.

Future work 🀸#

  • IPv6
  • Improve monitoring & set up alerts
  • Set up a third network for IOT devices

Final notes#

  • I can't count how many times the serial connection saved me. Without it I'd have bricked the router by making it inaccessible.
  • I think it's pretty insane how relatively easy this was to set up. There's just so much power that comes included with NixOS.

Thanks πŸ™#

I could not have done this project on my own. Thank you to all software contributors, authors, and maintainers. And a special thanks to: