Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 19 additions & 38 deletions net/openthread-br/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
include $(TOPDIR)/rules.mk

PKG_NAME:=openthread-br
PKG_SOURCE_DATE:=2025-06-12
PKG_SOURCE_VERSION:=2f3c799c7463c8f674754e65c53f78bc0bbcbd58
PKG_RELEASE:=2
PKG_VERSION:=2026.09.0
PKG_SOURCE_VERSION:=v$(PKG_VERSION)
PKG_RELEASE:=1

PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL=https://github.com/openthread/ot-br-posix.git
PKG_MIRROR_HASH:=fae1c7c88102f88bac32f8ee25e044b9a8781eef6b9acef39388e1e11a44a195
PKG_MIRROR_HASH:=d0c82691b95e0aa2112ca08f6ff544c23794eae632c8c01b040616a0d60f5aab

PKG_MAINTAINER:=Stijn Tintel <stijn@linux-ipv6.be>
PKG_LICENSE:=BSD-3-Clause
Expand All @@ -19,31 +19,25 @@ PKG_LICENSE_FILES:=LICENSE
include $(INCLUDE_DIR)/package.mk
include $(INCLUDE_DIR)/cmake.mk

define Package/luci-app-openthread
CATEGORY:=LuCI
SECTION:=luci
SUBMENU:=3. Applications
TITLE:=LuCI Support for OpenThread Border Router
DEPENDS:=+luci-base +luci-lua-runtime
endef

define Package/openthread-br
CATEGORY:=Network
SECTION:=net
TITLE:=OpenThread Border Router
DEPENDS:= \
+kmod-nft-core \
+kmod-nft-nat \
+kmod-tun \
+libblobmsg-json \
+libjson-c \
+cJSON \
+libmnl \
+libncurses \
+libnetfilter-queue \
+libnftnl \
Comment thread
LorbusChris marked this conversation as resolved.
+libreadline \
+libstdcpp \
+libubox \
+libubus \
+mdnsd \
+mdnsresponder
+libubus
endef

define Package/openthread-br/description
Expand All @@ -63,43 +57,30 @@ CMAKE_OPTIONS += \
-DOT_TARGET_OPENWRT=ON \
-DOTBR_BACKBONE_ROUTER=ON \
-DOTBR_BORDER_ROUTING=ON \
-DOTBR_DNSSD_DISCOVERY_PROXY=ON \
-DOTBR_DUA_ROUTING=ON \
-DOTBR_MDNS=mDNSResponder \
-DOTBR_MDNS=openthread \
-DOTBR_NAT64=OFF \
-DOTBR_NFTABLES=ON \
Comment thread
LorbusChris marked this conversation as resolved.
-DOTBR_OPENWRT=ON \
-DOTBR_PRODUCT_NAME=BorderRouter \
-DOTBR_REST=ON \
-DOTBR_SRP_ADVERTISING_PROXY=ON \
-DOTBR_SRP_SERVER_AUTO_ENABLE=ON \
-DOTBR_TREL=ON
-DOTBR_TREL=ON \
-DOTBR_VENDOR_NAME=OpenThread \
-DOTBR_VERSION=$(PKG_VERSION)

TARGET_CFLAGS += -DOPENTHREAD_POSIX_CONFIG_DAEMON_SOCKET_BASENAME=\\\"/var/run/openthread-%s\\\"

define Package/luci-app-openthread/install
$(INSTALL_DIR) \
$(1)/usr/lib/lua/luci/controller/admin \
$(1)/usr/lib/lua/luci/view/admin_thread \
$(1)/www/luci-static/resources
$(INSTALL_DATA) \
$(PKG_BUILD_DIR)/src/openwrt/controller/thread.lua \
$(1)/usr/lib/lua/luci/controller/admin
$(INSTALL_DATA) \
$(PKG_BUILD_DIR)/src/openwrt/view/admin_thread/* \
$(1)/usr/lib/lua/luci/view/admin_thread
$(INSTALL_DATA) \
$(PKG_BUILD_DIR)/src/openwrt/handle_error.js \
$(1)/www/luci-static/resources
endef

define Package/openthread-br/install
$(INSTALL_DIR) \
$(1)/etc/init.d \
$(1)/lib/netifd/proto \
$(1)/usr/sbin
$(1)/usr/sbin \
$(1)/usr/share/otbr
$(INSTALL_BIN) ./files/openthread-proto.sh $(1)/lib/netifd/proto/openthread.sh
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/* $(1)/usr/sbin
$(INSTALL_DATA) $(PKG_INSTALL_DIR)/usr/share/otbr/nftables-backend \
Comment thread
LorbusChris marked this conversation as resolved.
$(1)/usr/share/otbr
endef


$(eval $(call BuildPackage,luci-app-openthread))
$(eval $(call BuildPackage,openthread-br))
97 changes: 80 additions & 17 deletions net/openthread-br/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,82 @@ the package will likely result in more bug reports. As the package and its
dependencies are unlikely to fit in any router with small flash (16MB or less),
I don't see much point in making things configurable for reducing size either.

### Firewall support
### Vendor and product name

`OTBR_VENDOR_NAME` and `OTBR_PRODUCT_NAME` have to be set: the 2026.08.0 release removed
the built-in defaults, and otbr-agent exits with `Vendor name must be set.`
without them.

They are deliberately set to the values those defaults had, `OpenThread` and
`BorderRouter`, rather than to something OpenWrt specific. The pair forms the
MeshCoP service instance name as `<vendor> <product>`, which is the name shown
when adding the border router in a Thread client and the one already-paired
clients have recorded, so changing it would rename every existing user's border
router on upgrade.

OpenWrt uses firewall4 with nftables by default, but the OpenThread firewall
implementation uses IPTables and IPset. While we still support firewall3 with
IPTables, it's not a good idea to add new dependencies to old things.
Therefore, firewall support is disabled completely.
### Version string

This can be revised once the following feature request is implemented:
https://github.com/openthread/ot-br-posix/issues/1675
`OTBR_VERSION` is set to `PKG_VERSION`. Without it the build falls back to the
CMake project version, because the repacked source tree has no git directory
for `git describe` to read, so `otbr-agent --version` and the `Running ...`
line it logs on every start would report `0.3.0` rather than the release the
package was built from.

### mDNSResponder
The version test in the package CI matches on that string, so dropping this
option would make the package fail it again.

### Firewall support

The package depends on mDNSResponder. The alternative, Avahi, depends on D-Bus,
which is not something I feel comfortable with running on any router. While
there are Avahi packages without D-Bus support, using OpenThread Border Router
with Avahi requires libavahi-client, and this requires Avahi to be built with
D-Bus support.
OpenWrt uses firewall4 with nftables. The OpenThread posix platform's own
firewall (`OT_FIREWALL`) produces ipset/ip6tables rules, so it has always been
disabled here and no Thread ingress filter existed at all.

Since v2026.09.0 upstream provides exactly what
https://github.com/openthread/ot-br-posix/issues/1675 asked for: an in-process
nftables backend (`OTBR_NFTABLES`). It is enabled, so otbr-agent now installs
the Thread ingress filter and the NAT44 masquerade itself, through nftables in
an isolated `otbr` table, matching what the rest of the system uses. This
needs nf_tables kernel support at runtime, hence the `kmod-nft-core` and
`kmod-nft-nat` dependencies. `OT_FIREWALL` deliberately stays off: the
in-process backend replaces its ipset producer wholesale, and upstream's CI
builds the same pairing.

The build also writes a `/usr/share/otbr/nftables-backend` marker recording
which backend owns the rules. Nothing on an OpenWrt target reads it today (its
consumers are upstream's setup scripts, which this package does not install);
it is shipped as a debugging aid and for anything that later needs to tell the
backends apart.

### mDNS

The package uses OpenThread's internal mDNS implementation
(`-DOTBR_MDNS=openthread`), which is upstream's default. This drops the
mDNSResponder dependency entirely: no separate daemon, and no Avahi, whose
libavahi-client requirement would have pulled in D-Bus.

The internal implementation advertises on a single infrastructure interface,
the one selected by the `backbone_network` option. Anything that needs to be
announced on more than one interface still needs a general-purpose responder.

It coexists with umdns, which remains the provider for other packages'
services. Both bind the wildcard address on port 5353 with SO_REUSEADDR, which
is what admits the second bind and gets multicast delivered to both, and they
never contend for a name: OpenThread's mDNS names its host after the Thread
extended address, while umdns keeps `<hostname>.local`.

Only multicast reaches both. A unicast datagram to port 5353 is delivered to
one socket, so a unicast reply meant for one daemon can be received by the
other. umdns does set SO_REUSEPORT, but only on a retry after its own bind
fails, and that does not happen here because SO_REUSEADDR already admits the
bind, so no SO_REUSEPORT group forms in either start order.

### REST Server

The REST server is enabled to make this package compatible with Home Assistant.
It listens on 127.0.0.1 by default. `rest_listen_address` and
`rest_listen_port` can move it, but the API is unauthenticated and can read and
replace the Thread dataset — including the network key — so any non-loopback
address must be firewalled to trusted hosts.

### TREL support

Expand Down Expand Up @@ -88,9 +143,13 @@ config interface 'thread'
option verbose '0'
```

Prefix and verbose are optional. Everything else is required. The protocol
handler will fail if a required setting is missing. If something isn't working,
check ifstatus for the OpenThread interface:
Only backbone_network, device and radio_url are required; the protocol handler
fails the interface if one of them is missing, or if backbone_network names an
interface that has no device. Everything else — dataset, prefix, verbose,
rest_listen_address and rest_listen_port — is optional. See
[REST Server](#rest-server) before moving the REST API off the loopback
default. If something isn't working, check ifstatus for the OpenThread
interface:

```
# ifup thread
Expand Down Expand Up @@ -125,7 +184,11 @@ ubus call otbr threadstop

### LuCI

Creating a network in LuCI appears to be broken for the moment.
This package no longer ships a LuCI application; the web UI lives in the
`luci-app-openthread` package in the openwrt/luci repository
(https://github.com/openwrt/luci/pull/8871). Install that alongside this package
for a web interface, on a LuCI feed recent enough to carry it — older feeds will
not have the package yet.

### CLI

Expand Down
27 changes: 10 additions & 17 deletions net/openthread-br/files/openthread-proto.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,14 @@ proto_openthread_add_prefix() {
[ -n "$prefix" ] && $OTCTL prefix add $prefix
}

proto_openthread_check_service() {
service="$1"
ret=1
json_init
json_add_string name "$service"
ubus call service list "$(json_dump)" | jsonfilter -e '@[*].instances[*]["running"]' > /dev/null
ret=$?
json_cleanup

return "$ret"
}

proto_openthread_init_config() {
proto_config_add_array 'prefix:list(string)'
proto_config_add_boolean verbose
proto_config_add_string backbone_network
proto_config_add_string dataset
proto_config_add_string radio_url
proto_config_add_string foobar
proto_config_add_string rest_listen_address
proto_config_add_int rest_listen_port

available=1
no_device=1
Expand All @@ -61,7 +50,7 @@ proto_openthread_setup() {

mkdir -p /var/lib/thread

json_get_vars backbone_network dataset device radio_url verbose:0
json_get_vars backbone_network dataset device radio_url rest_listen_address rest_listen_port verbose:0

[ -n "$backbone_network" ] || proto_openthread_setup_error "$interface" MISSING_BACKBONE_NETWORK
proto_add_host_dependency "$interface" "" "$backbone_network"
Expand All @@ -71,13 +60,17 @@ proto_openthread_setup() {
[ -n "$device" ] || proto_openthread_setup_error "$interface" MISSING_DEVICE
[ -n "$radio_url" ] || proto_openthread_setup_error "$interface" MISSING_RADIO_URL

# run in subshell to prevent wiping json data needed for prefixes
( proto_openthread_check_service mdnsd ) || proto_openthread_setup_error "$interface" MISSING_SVC_MDNSD

opts="--auto-attach=0"
[ "$verbose" -eq 0 ] || append opts -v
append opts "-I$device"
append opts "-B$backbone_ifname"
# The REST API listens on 127.0.0.1 by default. Bind it elsewhere (e.g. a
# LAN address) to let remote clients such as Home Assistant reach it;
# leaving it unset keeps the loopback-only default. The REST API is
# unauthenticated and can both read and replace the Thread dataset, so any
# non-loopback address must be firewalled to trusted hosts.
[ -n "$rest_listen_address" ] && append opts "--rest-listen-address=$rest_listen_address"
[ -n "$rest_listen_port" ] && append opts "--rest-listen-port=$rest_listen_port"
append opts "$radio_url"
append opts "trel://$backbone_ifname"
# run in subshell to prevent wiping json data needed for prefixes
Expand Down

This file was deleted.

Loading