tl;dr for this week: I can browse the internet on an Android phone connected by wifi to my Liminix test device :-)

Read on for how we got there.


For the moment, wireless support is provided using the same pattern as NixWRT was using (originally copied from OpenWrt):

  • we are using the same kernel version as OpenWrt, and the multitude of patches they apply for wider device support. Usually this lags the mainline kernel by a couple of minor versions, because of all those patches.

  • this kernel is not configured for wifi

  • we build mac80211 and everything it depends on as modules from a more recent kernel version, using the kernel Backports Project which automatically patch the more recent kernel sources into something that will be compatible with the OpenWrt kernel.

It has to be said that this may be over-engineered for the current state of the OpenWrt kernel. The latest kernel version that backports can translate from is 5.15.92, and the OpenWrt kernel is 5.15.71, so … not a whole lot of gain. Maybe the backports project will start supporting 6.x kernels and make this worthwhile. I’m maintaining a watching brief to see if we can remove this step, but the code was mostly copied from NixWRT anyway so it didn’t really take that much longer than the simple approach would have done.

Anyway, having NixWRT code to copy/adapt here made it comparatively quick to reach the point of “I can see the device with a wifi scanner”

Everything else

To get a device from “shows up in a wifi scan” to “can connect to and reach the internet” needs a few more ingredients: it has to be able to give connecting peers their IP addresses, provide them with name service, forward packets, perform NAT for IPv4 etc.

rotuer.nix is the configuration file I created for my test device to do all of the above. As you may note from the comments at the top of the file, it’s still spike/experimental quality: less a preferred solution and more an exploration of the problem space.

DHCP and DNS service for the LAN

We’re using the excellent dnsmasq software which does both of these in one binary. It’s not a fully fledged recursive DNS server though, it needs to know where to send requests upstream. Therefore we also need to:

Make PPPoE service request DNS server addresses from upstream

We add the pppd option usepeerdns, then the ip-up script is called with $DNS1 and $DNS2 environment variables. We write these into the service outputs.

Resolvconf service

The days followed one another patiently. Right back at the beginning of the multiverse they had tried all passing at the same time, and it hadn’t worked. (Terry Pratchett, Wyrd Sisters)

Ordering constraints were a recurring theme of this week’s work.

We need to have DHCP service running even when the WAN is not up. Otherwise, imagine you have an internet outage and can’t login to the router to look at the logs and find out what’s wrong because the router won’t give your device an IP address.

So, dnsmasq needs to start before PPPoE is running. But it also needs to have the upstream nameserver addresses when PPPoE is running. Happily, it does 90% of the required work to make this happen: if configured with -resolv-file=/path/to/resolv.conf and if the directory containing that file exists, it will watch the directory (using polling or inotify or something) and read the file whenever it’s created or updated.

The missing piece is to create that file, then. We do this in a service called resolvconf which depends on the pppoe service. It needs to set file permissions that the dnsmasq service can read, so this was a good time to introduce a bit of support/infra (a shell functions file that service scripts can source) to make it easy for services to get this right.

Bridge wired LAN with wireless

Not strictly necessary for wireless alone, but there are also LAN ports on the test router which I’d like to be able to use. it turns out you can’t add a device to a bridge unless that device is both “administratively up” and “operationally up”, which is a poser for wlan0 because it’s not operationally up until hostapd has started up and done various bits of twiddling. hostapd doesn’t have readiness notifications, so we can’t rely on it to tell us when the interface is ready. Did I say that ordering constraints were a theme this week?

What we do here is write a small program that listens for Rtnetlink events, and sends an s6 readiness notification when it sees that wlan0 is ready to use. Then we can have a service depending on it that adds the device to the bridge.

Packet forwarding

Is a simple matter of twiddling files in /proc/sys/net/ipv4/

NAT for the LAN devices

I’ve decided to go all-in on nftables here instead of supporting the legacy (apparently, now) iptables. I could really use some good nftables documentation, but it currently looks like the only way to get that would be to write it myself.


I could spend a lot more time on cleaning this script up and incorporating it into Liminix proper - and on supporting IPv6, which I think is a hard requirement for a civilised society - and at some point I will have to. But: it’s all outside scope for phase 1, which is “get the hardware devices that worked with NixWRT to work again with Liminix”. So I probably should put that on the back burner for the moment, and turn instead to:

  • make the ath10k radio (needed for 5GHz) work in this gl-ar750
  • find out if there’s a switch for the LAN ports (I assume there is) and make it work if so
  • make the gl-mt300a and mt300n work

The latter two are both building successfully, but do they boot? Don’t know yet.