8 min read

An OpenThread Quest: My IPv6 Border Router

An OpenThread Quest: My IPv6 Border Router

This is the first post on what I expect to be a long and successful list. Yes, let me dream, I know that it will be challenging.

So, let's start with my agenda: I like open standards, and I have grown to hate ZigBee and WiFi protocols for home automation. WiFi is not really a suitable protocol for home automation --too many wifi devices will degrade your home network, and battery powered devices really dislike this high-consumption protocol. ZigBee... is a good idea, but all the ecosystem around it seems to be built on royalties, manfucaturer specific implementations, closed hubs, and (lots of) reverse engineering. Moreover, the DIY scene for ZigBee doesn't look to good in my experience.

Thread is a protocol that seems suitable for home automation, and OpenThread is an open implementation that seems to be quite robust. You can check it at https://openthread.io/.

If you want something immediately usable with no tinkering, stop reading. If you like my agenda, my opinions and/or want to dip your toes in OpenThread DIY, then you may enjoy this series. (Yes! I'm calling it a series even before I start my first entry! Sue me!).

Embracing IPv6

Thread networks will typically have a Border Router, which is a router that connects the thread devices with your LAN. There is a guide here: https://openthread.io/guides/border-router but it's very focused on giving you a MVP that is IPv4 compatible. I have IPv6 at home and I want everything to be IPv6 native. And I want to use my LAN network. That's what I will be talking about in this blog entry.

"Typical" home network with an OpenThread Border Router

Get a Thread Dongle

The fundamental thing that you need in a OpenThread border router is Thread connectivity. You need something similar to this makerdiary product or this chinese equivalent USB. Any nRF52840 board with USB should be adequate. No affiliation links here, nor any kind of personal review; I am just giving away some pointers.

You will need to flash an appropriate firmware. See this entry from the future for more information on how to get started with the dongle.

Preparing the Raspberry Pi

Get a Raspberry Pi. Get a MicroSD card. Write a Raspberry Pi OS Lite onto the card. Configure the SSH (and remember, if you are using WiFi, to configure that too). Plug everything. Connect to the Raspberry Pi and perform all the sudo raspi-config first-time config.

Ready? Let's start with the tricky stuff.

Basic IPv6 foundation

In my experience, you should get an IPv6 addresses out of the box. Let's say that the Raspberry has the IP 2001:db8:100::42 (being 2001:db8:100::/64 your LAN range). I will also assume that 2001:db8:200::/64 is a range you control where you will build your OpenThread network. If you don't have an assignment bigger than /64 then you must do things differently, and I really don't know how to do it. I am myself using Hurricane Electric free Tunnel Broker IPv6 service and I have a /48 so that's what I am using.

Because later on we will end up installing (indirectly) the network-manager package, let's be proactive and install it now:

$ sudo apt install network-manager
$ sudo apt purge openresolv dhcpcd5
Install Network-Manager, uninstall the unneeded packages

You will need to set the managed flag in the /etc/NetworkManager/NetworkManager.conf as this shows:

[ifupdown]
managed=true

If you are using SLAAC: then you want some kind of static IP on the raspberry, and the easiest way is to use hardware-based addresses. This is achieved with the following command:

$ nmcli con show
# Default name is "Wired connection 1". Ugly name indeed
$ sudo nmcli con mod "Wired connection 1" ipv6.addr-gen-mode eui64

That command changes the configuration and after next reboot the IPv6 of the Raspberry will be a deterministic one --the IP won't change unless the Raspberry is substituted.

Look mum, I'm a router

net.ipv6.conf.all.forwarding=1

Well, that was anticlimactic. But basically, a router is a thing that forwards packets and that is achieved with that flag. You must put it in the /etc/sysctl.conf file. If you don't want to reboot, you can also run sysctl -w net.ipv6.conf.all.forwarding=1.

Look mum, I'm a Border Router

We will be using the opensource implementation of the border router provided by OpenThread, available in GitHub. The guide is available in the OpenThread website. That documentation focus on a certain configuration principles: build an Access Point at the Raspberry Pi with IPv6<->IPv4 tunneling. If that's ok, then you can ignore this blog entry and go there. But if you are ok with my IPv6 maximalist approach, keep reading.

Install git and become root; you can leave source code in /root if that's ok for you.

$ git clone https://github.com/openthread/ot-br-posix
$ cd ot-br-posix
$ export NAT64=0
$ export DNS64=0
$ export DHCPV6_PD=0
$ export NETWORK_MANAGER=0
$ export BACKBONE_ROUTER=0
$ ./script/bootstrap
$ ./script/setup
Build and configuration of the Border Router (POSIX implementation)

The last command will be running for a while --specially if you are using old Raspberry Pi models. Note that we are disabling the NETWORK_MANAGER because that configures the host Access Point mode, which we do not want. And the *64 features are needed if you plan to have IPv4 compatibility, but I am skipping that completely.

My dongle nRF52840 appears at /dev/ttyACM0 (check if that's your case). With that in mind, configure the otbr-agent by editing /etc/default/otbr-agent. Mine has:

OTBR_AGENT_OPTS="-I wpan0 spinel+hdlc+uart:///dev/ttyACM0"

You can now reboot the Raspberry. You may be able to restart services instead but... it's easier and safer just to reboot again. If everything's good, you will now see a new network interface called wpan0 (you can see the interfaces by doing ip link). The web interface will be available at port 80, you can see it with any browser (e.g. http://[2001:db8:100::42]

If there's something missing, you can check the Verify services and Verify RCP steps from the official build guide.

Once rebooted, the otbr-* services should be up and running and we can form an OpenThread network. This is done with the web interface, as explained here. It may look like this:

Example of the "Form" form, with random data

At this point you can start commissioning devices to this network, but we still lack some things. We need to address certain router advertisement capabilities at the Border Router. At the OpenThread level we need to configure the prefix for those public IPv6 addresses. We also need to add a route for the LAN IPv6 prefix. Those things can be done as follows:

$ ot-ctl prefix add 2001:db8:200::/64 paros
$ ot-ctl route add 2001:db8:100::/64 s med
$ ot-ctl netdata register
Set the IPv6 prefix on the OpenThread stack

After those commands, you will see that the wpan0 interface will have a valid IPv6 on the range 2001:db8:200::/64. However that configuration is ephemereal --the configuration is lost after a reboot. However... it seems that I'm not the first to stumble upon this. [Note: I have not participated in that thread. It's just a coincidence.]

My solution involves a systemd service file. Put the following in /etc/systemd/system/otbr-additionalprefix.service (remember to tweak to your prefix):

[Unit]
Description=Additional IPv6 prefix for the OpenThread stack
Requires=otbr-agent.service
After=otbr-agent.service

[Service]
Type=oneshot
# Awful workaround (keep reading)
ExecStart=sleep 120
# End of awful workaround
ExecStart=ot-ctl prefix add 2001:db8:200::/64 paros
ExecStart=ot-ctl route add 2001:db8:100::/64 s med
ExecStart=ot-ctl netdata register
# The following is optional; it gives an iconic IP
# to the BR (in the OpenThread network context)
ExecStart=ip a a 2001:db8:200::1 dev wpan0
## If your systemd supports `Restart` for oneshot services,
## then you can remove the sleep workaround and uncomment
## the following lines, which make more sense
#Restart=on-failure
#RestartSec=30

[Install]
WantedBy=multi-user.target
Alias=otbr-additionalprefix.service

Enable the service and restart the machine to check that it is working as intended. It will be working as intended when the wpan0 interface has a proper IP and the output of ot-ctl prefix shows your prefix.

Becoming a full-fledged IPv6 router

There is a key aspect of IPv6 way-of-doing-things that is Router Advertisement (commonly written RA). The border router needs to publicly state that it is a router and what ranges it is working. This will solve routability and also proper IP acquisition. I will be assuming that SLAAC is supported on the OT devices; it is the simplest way of getting hardware to have deterministic unique static IPs. Also keep in mind: several prior configuration steps also require SLAAC on the Thread devices --like the mesh-local prefix or the additional IPv6 prefix.

We will be using the radvd package (install it with apt-get). This is a sample configuration of /etc/radvd.conf:

interface wpan0
{
    # On this interface, we are publishing the prefix.
    # It is the canonical prefix for OpenThread devices.
 
    AdvSendAdvert on;

    MinRtrAdvInterval 5;
    MaxRtrAdvInterval 15;

    prefix 2001:db8:200::/64
    {
        AdvOnLink on; 
        AdvAutonomous on;
    };
};

interface eth0
{
    # On this interface we are publishing ourselves
    # as the main routing option to access it.
    # I.e. we are saying that the OpenThread network
    # addresses are accessed through this border router.
    
    AdvSendAdvert on;

    MinRtrAdvInterval 5;
    MaxRtrAdvInterval 15;

    route 2001:db8:200::/64 {};
}

Restart the radvd daemon once you have a proper configuration file with systemctl restart radvd.service.

Making your other router play nice with its new friend

At this point, you should be able to ping Thread devices from within any machine in your LAN --i.e. try it from your laptop!

Things are working because radvd is doing its job and your desktop is getting the routes advertised by the radvd service. You can see those routes by doing ip -6 route (from the desktop). There will be a line that resembles this:

2001:db8:200::/64 via fe80::dead:beef:caffe:0042 dev enp11s0 proto ra metric 100 pref medium 

However, you may want to have the global IPs publicly addressables, and that means making your "internet router" behave properly. I have pfSense and I have observed that it doesn't like to follow RA given from within the LAN network, so I did some things:

  • Add a new gateway. You can use the fe80:: IPv6 address of your Border Router machine, assuming that both routers are link-local between them.
  • Add a new static route. The prefix is the 2001:db8:200::/64 and the gateway is the previous one.
  • Add some extra fireway rules; I like to allow outside ICMP traffic to IPv6 and you need to specify to pfSense that 2001:db8:200::/64 is allowed in LAN.

Your mileage may vary.

References

Building an IPv6 router with GNU/Linux
Some generic configuration of IPv6 routers (commands a bit outdated, but correct general information)
OpenThread Border Router
The official guide for the OpenThread Border Router
openthread/openthread
OpenThread released by Google is an open-source implementation of the Thread networking protocol - openthread/openthread
OpenThread CLI Reference --useful for the ot-ctl usage