Thread Border Router - Bidirectional IPv6 Connectivity and DNS-Based Service Discovery

1. Introduction


What is a Thread Border Router?

Thread is an IP-based low-power wireless mesh networking protocol that enables secure device-to-device and device-to-cloud communications. Thread networks can adapt to topology changes to avoid single point of failure.

A Thread Border Router connects a Thread network to other IP-based networks, such as Wi-Fi or Ethernet. A Thread network requires a Border Router to connect to other networks. A Thread Border Router minimally supports the following functions:

  • Bidirectional IP connectivity between Thread and Wi-Fi/Ethernet networks.
  • Bidirectional service discovery via mDNS (on Wi-Fi/Ethernet link) and SRP (on Thread network).
  • Thread-over-infrastructure that merges Thread partitions over IP-based links.
  • External Thread Commissioning (for example, a mobile phone) to authenticate and join a Thread device to a Thread network.

OpenThread Border Router (OTBR) released by Google is an open-source implementation of the Thread Border Router.

What you'll build

In this codelab, you're going to set up a Thread Border Router and connect your mobile phone to a Thread End Device via the Border Router.

What you'll learn

  • How to set up OTBR
  • How to form a Thread network with OTBR
  • How to build an OpenThread CLI device with the SRP feature
  • How to register a service with SRP
  • How to discover and reach a Thread end device.

What you'll need

  • A Raspberry Pi 3/4 device and a SD card with at least 8 GB capability.
  • 2 Nordic Semiconductor nRF52840 dev boards.
  • A Wi-Fi AP without IPv6 Router Advertisement Guard enabled on the router.
  • An iOS phone with at least iOS 14 or Android phone with at least Android 8.1.

2. Setup OTBR

Setup Raspberry Pi

It is simple to set up a fresh Raspberry Pi device with the rpi-imager tool by following the instructions on (instead of using the latest Raspberry Pi OS in the tool, download 2021-05-07-raspios-buster-armhf-lite by yourself). To complete the mobile phone steps in this codelab, you need to connect the Raspberry Pi to a Wi-Fi AP. Follow this guide to set up wireless connectivity. It is convenient to log in to the Raspberry Pi with SSH, you can find instructions here.

Get OTBR code

Log in to your Raspberry Pi and clone ot-br-posix from GitHub:

$ git clone --depth 1

Build and install OTBR

OTBR has two scripts that bootstrap and set up the Thread Border Router:

$ cd ot-br-posix
$ ./script/bootstrap
$ INFRA_IF_NAME=wlan0 ./script/setup

OTBR works on both a Thread interface and infrastructure network interface (e.g. Wi-Fi/Ethernet) which is specified with INFRA_IF_NAME. The Thread interface is created by OTBR itself and named wpan0 by default and the infrastructure interface has a default value of wlan0 if INFRA_IF_NAME is not explicitly specified. If your Raspberry Pi is connected by an Ethernet cable, specify the Ethernet interface name (e.g. eth0):

$ INFRA_IF_NAME=eth0 ./script/setup

Check if OTBR is successfully installed:

$ sudo service otbr-agent status
● otbr-agent.service - Border Router Agent
   Loaded: loaded (/lib/systemd/system/otbr-agent.service; enabled; vendor preset: enabled)
   Active: activating (auto-restart) (Result: exit-code) since Mon 2021-03-01 05:43:38 GMT; 2s ago
  Process: 2444 ExecStart=/usr/sbin/otbr-agent $OTBR_AGENT_OPTS (code=exited, status=2)
 Main PID: 2444 (code=exited, status=2)

It is expected that the otbr-agent service is not active, because it requires an RCP chip to run.

Reboot the Raspberry Pi to take the changes to effect.

Build and flash RCP firmware

OTBR supports a 15.4 radio chip in Radio Co-Processor (RCP) mode. In this mode, the OpenThread stack is running on the host side and transmits/receives frames over the IEEE802.15.4 transceiver.

Follow step 4 of the Build a Thread network with nRF52840 boards and OpenThread codelab to build and flash a nRF52840 RCP device:

$ script/build nrf52840 USB_trans

Start OTBR and verify status

Connect the nRF52840 board to your Raspberry Pi and start the otbr-agent service:

$ sudo service otbr-agent restart

Verify that the otbr-agent service is active:

$ sudo service otbr-agent status
● otbr-agent.service - Border Router Agent
   Loaded: loaded (/lib/systemd/system/otbr-agent.service; enabled; vendor preset: enabled)
   Active: active (running) since Mon 2021-03-01 05:46:26 GMT; 2s ago
 Main PID: 2997 (otbr-agent)
    Tasks: 1 (limit: 4915)
   CGroup: /system.slice/otbr-agent.service
           └─2997 /usr/sbin/otbr-agent -I wpan0 -B wlan0 spinel+hdlc+uart:///dev/ttyACM0

Mar 01 05:46:26 raspberrypi otbr-agent[2997]: Stop publishing service
Mar 01 05:46:26 raspberrypi otbr-agent[2997]: [adproxy] Stopped
Mar 01 05:46:26 raspberrypi otbr-agent[2997]: PSKc is not initialized
Mar 01 05:46:26 raspberrypi otbr-agent[2997]: Check if PSKc is initialized: OK
Mar 01 05:46:26 raspberrypi otbr-agent[2997]: Initialize OpenThread Border Router Agent: OK
Mar 01 05:46:26 raspberrypi otbr-agent[2997]: Border router agent started.
Mar 01 05:46:26 raspberrypi otbr-agent[2997]: [INFO]-CORE----: Notifier: StateChanged (0x00038200) [NetData PanId NetName ExtPanId]
Mar 01 05:46:26 raspberrypi otbr-agent[2997]: [INFO]-PLAT----: Host netif is down

3. Form a Thread network

There is an ot-ctl command which can be used to control the otbr-agent service. ot-ctl accepts all OpenThread CLI commands, see OpenThread CLI Guide for more details.

Form a Thread network with OTBR:

$ sudo ot-ctl dataset init new
$ sudo ot-ctl dataset commit active
$ sudo ot-ctl ifconfig up
$ sudo ot-ctl thread start

Wait a few seconds, we should be able to see that OTBR is acting as a Thread leader and there is an off-mesh-routable (OMR) prefix in the Thread Network Data:

$ sudo ot-ctl state
$ sudo ot-ctl netdata show
fd76:a5d1:fcb0:1707::/64 paos med 4000
fd49:7770:7fc5:0::/64 s med 4000
44970 5d c000 s 4000
44970 01 9a04b000000e10 s 4000
$ sudo ot-ctl ipaddr      

4. Set Up SRP Client End Device

Build and flash OT CLI

Follow step 5 of the Build a Thread network with nRF52840 boards and OpenThread codelab to build and flash a nRF52840 CLI end device.

But instead of having OT_COMMISSIONER and OT_JOINER enabled, the CLI node requires OT_SRP_CLIENT and OT_ECDSA features.

So the full build invocation should look like this:

$ script/build nrf52840 USB_trans -DOT_SRP_CLIENT=ON -DOT_ECDSA=ON

Join the OTBR network

To join the Thread network created by the otbr-agent service, we need to get the Active Operational Dataset from the OTBR device. Let's go back to the otbr-agent command line and get the active dataset:

$ sudo ot-ctl dataset active -x

Return to the SRP client node screen session and set the active dataset:

> dataset set active 0e080000000000010000000300001235060004001fffe002083d3818dc1c8db63f0708fda85ce9df1e662005101d81689e4c0a32f3b4aa112994d29692030f4f70656e5468726561642d35326532010252e204103f23f6b8875d4b05541eeb4f9718d2f40c0302a0ff

Then, start the Thread interface:

> ifconfig up
> thread start

Wait for a few seconds and verify if joining the Thread network is successful:

> state
> netdata show
fd76:a5d1:fcb0:1707::/64 paos med 4000
fd49:7770:7fc5:0::/64 s med 4000
44970 5d c000 s 4000
44970 01 9a04b000000e10 s 4000
> ipaddr

Make sure that the network data matches the one printed on OTBR. We can now ping OTBR's OMR address:

> ping fd76:a5d1:fcb0:1707:f3c7:d88c:efd1:24a9
> 16 bytes from fd76:a5d1:fcb0:1707:f3c7:d88c:efd1:24a9: icmp_seq=1 hlim=64 time=49ms

5. Publish the Service on the End Device

mDNS has been widely used for publishing DNS-SD service on link-local. But multicast messages consume too much bandwidth and will exhaust the battery for low power devices quickly. Thread uses the unicast SRP protocol to register their services with the Border Router and relies on the Border Router to advertise the services on the Wi-Fi or Ethernet link.

We can register a service with the srp client command.

Go to the SRP client node screen session and auto-start the SRP client:

> srp client autostart enable

Set the hostname that will be advertised on the Wi-Fi/Ethernet link:

> srp client host name ot-host

For a device on the Wi-Fi/Ethernet link to reach a Thread end device, the OMR address of the end device needs to be advertised:

> srp client host address fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927

At the end, register a fake _ipps._tcp service:

> srp client service add ot-service _ipps._tcp 12345

Wait a few seconds and we should be able to see the service registered:

> srp client service
instance:"ot-service", name:"_ipps._tcp", state:Registered, port:12345, priority:0, weight:0

We have completed all setup work and the _ipps._tcp service should have been advertised on the Wi-Fi/Ethernet link. It's time to discover and reach the end device now!

6. Discover the Service

Discover the service with a mobile phone


We use the Service Browser App to discover mDNS services with the Android phone, an equivalent App can also be found for iOS mobile devices. Open the App and the service _ipps._tcp should just show up.

Discover the service with a Linux host

If you want to discover the service from another Linux host, you can use the avahi-browse command.

Install avahi-daemon and avahi-utils:

$ sudo apt-get install -y avahi-daemon avahi-utils

Resolve the service:

$ sudo service avahi-daemon start # Ensure the avahi daemon is started.
$ avahi-browse -r _ipps._tcp
+ wlan0 IPv6 ot-service                                    Secure Internet Printer local
= wlan0 IPv6 ot-service                                    Secure Internet Printer local
   hostname = [ot-host.local]
   address = [fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927]
   port = [12345]
   txt = []

Discover the service with a macOS host

You can use dns-sd on macOS to resolve the service:

$ dns-sd -Z _ipps._tcp local.
Browsing for _ipps._tcp.local.
DATE: ---Sun 14 Mar 2021---
21:31:42.125  ...STARTING...

; To direct clients to browse a different domain, substitute that domain in place of '@'
lb._dns-sd._udp                                 PTR     @

; In the list of services below, the SRV records will typically reference dot-local Multicast DNS names.
; When transferring this zone file data to your unicast DNS server, you'll need to replace those dot-local
; names with the correct fully-qualified (unicast) domain name of the target host offering the service.

_ipps._tcp                                      PTR     ot-service._ipps._tcp
ot-service._ipps._tcp                           SRV     0 0 12345 ot-host.local. ; Replace with unicast FQDN of target host
ot-service._ipps._tcp                           TXT     ""

7. Ping the End Device

Ping from a mobile phone

Take the Pixel phone as an example, we can find out the OMR address of the previously registered service "ot-service" in the details page of the service instance in the Service Browser App.

bb992962e68d250b.png 888daa1df1e1a9bf.png

We can now ping the OMR address with another Network Analyzer App.

Unfortunately, the Android version of the Network Analyzer App doesn't support mDNS queries for the ping utility and we cannot ping the hostname ot-host.local directly (we can ping the hostname with the iOS version of the App).

Ping from a Linux/macOS host

Thread Border Router sends ICMPv6 Router Advertisements (RA) to advertise prefixes (via Prefix Information Option) and routes (via Route Information Option) on the Wi-Fi/Ethernet link.

Prepare Linux host

It is important to make sure that RA and RIO are enabled on your host:

  1. net.ipv6.conf.wlan0.accept_ra should be at least 1 if ip forwarding is not enabled, and 2 otherwise.
  2. net.ipv6.conf.wlan0.accept_ra_rt_info_max_plen should not be smaller than 64.

the accept_ra is defaulted to 1 for most distributions. But there may be other network daemons which will override this option (for example, dhcpcd on Raspberry Pi will override accept_ra to 0). You can check the accept_ra value with:

$ sudo sysctl -n net.ipv6.conf.wlan0.accept_ra

And set the value to 1 (or 2 in case IP forwarding is enabled) with:

$ sudo sysctl -w net.ipv6.conf.wlan0.accept_ra=1
Net.ipv6.conf.wlan0.accept_ra = 1

The accept_ra_rt_info_max_plen option on most Linux distributions is default to 0, set it to 64 with:

$ sudo sysctl -w net.ipv6.conf.wlan0.accept_ra_rt_info_max_plen=64
net.ipv6.conf.wlan0.accept_ra_rt_info_max_plen = 64

The change will be lost after rebooting the host. For example, append below commands to /etc/sysctl.conf to permanently enable RIO:

$ net.ipv6.conf.wlan0.accept_ra_rt_info_max_plen = 64

It may be too late to change those configurations because the OTBR has already been sending RA messages and the interval between two unsolicited RA messages could be several hundred seconds. One way is to disconnect and reconnect to Wi-Fi AP to send Router Solicitation messages so that OTBR will respond with solicited RAs. Another option is to restart the Border Routing function on the Border Router:

$ sudo ot-ctl br disable
$ sudo ot-ctl br enable

If you are trying to reconnect Wi-Fi or restart Ethernet interface, make sure that dhcpcd is not used for managing your WiFi-/Ethernet IPv6 network. Because dhcpcd always overrides the accept_ra option everytime the interface is restarted and your accept_ra configuration will be lost. Append below lines to the dhcpcd configuration file (e.g. /etc/dhcpcd.conf) to explicitly disable IPv6 in dhcpcd:


You need to reboot to take the change into effect.

Prepare macOS host

Both accept_ra* options are enabled by default, but you need to upgrade your system to at least macOS Big Sur.

Ping the hostname or IPv6 address

We can now ping the hostname ot-host.local with command ping -6 (ping6 for macOS):

$ ping -6 ot-host.local.
PING ot-host.local.(fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927 (fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927)) 56 data bytes
64 bytes from fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927 (fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927): icmp_seq=1 ttl=63 time=170 ms
64 bytes from fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927 (fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927): icmp_seq=2 ttl=63 time=64.2 ms
64 bytes from fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927 (fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927): icmp_seq=3 ttl=63 time=22.8 ms
64 bytes from fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927 (fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927): icmp_seq=4 ttl=63 time=37.7 ms
64 bytes from fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927 (fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927): icmp_seq=5 ttl=63 time=28.7 ms

This command may fail on Linux hosts with the "Name or service not known" error. That's because the ping command is not resolving the ot-host.local. name with mDNS queries. Open /etc/nsswitch.conf and add mdns6_minimal to the line starts with hosts:

hosts:          files mdns4_minimal mdns6_minimal dns

Of course, you can always ping the IPv6 address directly:

$ ping -6 fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927
PING fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927(fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927) 56 data bytes
64 bytes from fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927: icmp_seq=1 ttl=63 time=32.9 ms
64 bytes from fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927: icmp_seq=2 ttl=63 time=27.8 ms
64 bytes from fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927: icmp_seq=3 ttl=63 time=29.9 ms
64 bytes from fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927: icmp_seq=4 ttl=63 time=73.5 ms
64 bytes from fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927: icmp_seq=5 ttl=63 time=26.4 ms

8. End Device Unpublish the Service

To remove the address and service registered from the SRP client node:

> srp client host remove

You should not be able to discover the _ipps._tcp service now.

9. Congratulations

Congratulations, you've successfully set up OTBR as a Thread Border Router to provide bidirectional IP connectivity and service discovery for Thread end devices.

What's next?

Check out some of these codelabs...

Reference docs