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
Setup Raspberry Pi
It is simple to set up a fresh Raspberry Pi device with the
rpi-imager tool by following the instructions on raspberrypi.org (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 login to the Raspberry Pi with SSH, you can find instructions here.
Get OTBR code
Login to your Raspberry Pi and clone
ot-br-posix from GitHub:
$ git clone https://github.com/openthread/ot-br-posix.git --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
INFRA_IF_NAME is not explicitly specified. If your Raspberry Pi is connected by an Ethernet cable, specify the Ethernet interface name (e.g.
$ 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 this codelab to build and flash a nRF52840 RCP device. You need an additional option
-DOT_THREAD_VERSION=1.2 for the build step:
$ script/build nrf52840 USB_trans -DOT_THREAD_VERSION=1.2
Start OTBR and verify status
Connect the nRF52840 board to your Raspberry Pi and start the
$ 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: Stop publishing service Mar 01 05:46:26 raspberrypi otbr-agent: [adproxy] Stopped Mar 01 05:46:26 raspberrypi otbr-agent: PSKc is not initialized Mar 01 05:46:26 raspberrypi otbr-agent: Check if PSKc is initialized: OK Mar 01 05:46:26 raspberrypi otbr-agent: Initialize OpenThread Border Router Agent: OK Mar 01 05:46:26 raspberrypi otbr-agent: Border router agent started. Mar 01 05:46:26 raspberrypi otbr-agent: [INFO]-CORE----: Notifier: StateChanged (0x00038200) [NetData PanId NetName ExtPanId] Mar 01 05:46:26 raspberrypi otbr-agent: [INFO]-PLAT----: Host netif is down
There is an
ot-ctl command which can be used to control the
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 Done $ sudo ot-ctl dataset commit active Done $ sudo ot-ctl ifconfig up Done $ sudo ot-ctl thread start Done
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 leader Done $ sudo ot-ctl netdata show Prefixes: Prefixes: fd76:a5d1:fcb0:1707::/64 paos med 4000 Routes: fd49:7770:7fc5:0::/64 s med 4000 Services: 44970 5d c000 s 4000 44970 01 9a04b000000e10 s 4000 Done $ sudo ot-ctl ipaddr fda8:5ce9:df1e:6620:0:ff:fe00:fc11 fda8:5ce9:df1e:6620:0:0:0:fc38 fda8:5ce9:df1e:6620:0:ff:fe00:fc10 fd76:a5d1:fcb0:1707:f3c7:d88c:efd1:24a9 fda8:5ce9:df1e:6620:0:ff:fe00:fc00 fda8:5ce9:df1e:6620:0:ff:fe00:4000 fda8:5ce9:df1e:6620:3593:acfc:10db:1a8d fe80:0:0:0:a6:301c:3e9f:2f5b Done
Build and flash OT CLI
Follow step 5 of this codelab to build and flash a nRF52840 CLI end device. But instead of having
OT_JOINER enabled, the CLI node requires
$ 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 0e080000000000010000000300001235060004001fffe002083d3818dc1c8db63f0708fda85ce9df1e662005101d81689e4c0a32f3b4aa112994d29692030f4f70656e5468726561642d35326532010252e204103f23f6b8875d4b05541eeb4f9718d2f40c0302a0ff Done
Return to the SRP client node screen session and set the active dataset:
> dataset set active 0e080000000000010000000300001235060004001fffe002083d3818dc1c8db63f0708fda85ce9df1e662005101d81689e4c0a32f3b4aa112994d29692030f4f70656e5468726561642d35326532010252e204103f23f6b8875d4b05541eeb4f9718d2f40c0302a0ff Done
Then, start the Thread interface:
> ifconfig up Done > thread start Done
Wait for a few seconds and verify if joining the Thread network is successful:
> state child Done > netdata show Prefixes: fd76:a5d1:fcb0:1707::/64 paos med 4000 Routes: fd49:7770:7fc5:0::/64 s med 4000 Services: 44970 5d c000 s 4000 44970 01 9a04b000000e10 s 4000 Done > ipaddr fd76:a5d1:fcb0:1707:d3dc:26d3:f70b:b927 fda8:5ce9:df1e:6620:0:ff:fe00:4001 fda8:5ce9:df1e:6620:ed74:123:cc5d:74ba fe80:0:0:0:d4a9:39a0:abce:b02e Done
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 Done > 16 bytes from fd76:a5d1:fcb0:1707:f3c7:d88c:efd1:24a9: icmp_seq=1 hlim=64 time=49ms
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 Done
Set the hostname that will be advertised on the Wi-Fi/Ethernet link:
> srp client host name ot-host Done
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 Done
At the end, register a fake
> srp client service add ot-service _ipps._tcp 12345 Done
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 Done
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!
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
$ 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 =  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 "" ...
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.
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:
net.ipv6.conf.wlan0.accept_rashould be at least
1if ip forwarding is not enabled, and
net.ipv6.conf.wlan0.accept_ra_rt_info_max_plenshould not be smaller than
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
0). You can check the
accept_ra value with:
$ sudo sysctl -n net.ipv6.conf.wlan0.accept_ra 0
And set the value to
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
accept_ra_rt_info_max_plen option on most Linux distributions is default to
0, set it to
$ 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 Done $ sudo ot-ctl br enable Done
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
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: 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 ...
To remove the address and service registered from the SRP client node:
> srp client host remove Done
You should not be able to discover the
_ipps._tcp service now.
Congratulations, you've successfully set up OTBR as a Thread Border Router to provide bidirectional IP connectivity and service discovery for Thread end devices.
Check out some of these codelabs...