--- /dev/null
+# SPDX-FileCopyrightText: 2022-2023 Stijn Tintel <stijn@linux-ipv6.be>
+# SPDX-License-Identifier: GPL-2.0-only
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=openthread-br
+PKG_SOURCE_DATE:=2023-08-01
+PKG_SOURCE_VERSION:=1738d8cd8b42106c2ef1262fbbac2f06beab83ba
+PKG_RELEASE:=1
+
+PKG_SOURCE_PROTO:=git
+PKG_SOURCE_URL=https://github.com/openthread/ot-br-posix.git
+PKG_MIRROR_HASH:=7eb740d1a0663aae7969940f8a8b06879524dd62ae7842f46a160cae54ee8417
+
+PKG_MAINTAINER:=Stijn Tintel <stijn@linux-ipv6.be>
+PKG_LICENSE:=BSD-3-Clause
+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
+endef
+
+define Package/openthread-br
+ CATEGORY:=Network
+ SECTION:=net
+ TITLE:=OpenThread Border Router
+ DEPENDS:= \
+ +libblobmsg-json \
+ +libjson-c \
+ +libncurses \
+ +libnetfilter-queue \
+ +libreadline \
+ +libstdcpp \
+ +libubox \
+ +libubus \
+ +mdnsd \
+ +mdnsresponder
+endef
+
+define Package/openthread-br/description
+ A Thread border router for POSIX-based platforms.
+endef
+
+define Package/openthread-br/conffiles
+/var/lib/thread
+endef
+
+CMAKE_OPTIONS += \
+ -DOT_BORDER_ROUTER:BOOL=ON \
+ -DOT_BORDER_ROUTING_NAT64:BOOL=ON \
+ -DOT_CHANNEL_MANAGER:BOOL=ON \
+ -DOT_CHANNEL_MONITOR:BOOL=ON \
+ -DOT_COMMISSIONER:BOOL=ON \
+ -DOT_ECDSA:BOOL=ON \
+ -DOT_FIREWALL:BOOL=OFF \
+ -DOT_SERVICE:BOOL=ON \
+ -DOT_SRP_CLIENT:BOOL=ON \
+ -DOT_SRP_SERVER:BOOL=ON \
+ -DOTBR_BACKBONE_ROUTER:BOOL=ON \
+ -DOTBR_BORDER_ROUTING:BOOL=ON \
+ -DOTBR_DNSSD_DISCOVERY_PROXY:BOOL=ON \
+ -DOTBR_DUA_ROUTING:BOOL=ON \
+ -DOTBR_MDNS=mDNSResponder \
+ -DOTBR_OPENWRT:BOOL=ON \
+ -DOTBR_REST:BOOL=ON \
+ -DOTBR_SRP_ADVERTISING_PROXY:BOOL=ON \
+ -DOTBR_SRP_SERVER_AUTO_ENABLE:BOOL=ON \
+ -DOTBR_TREL:BOOL=ON
+
+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)/var/lib/thread
+ $(INSTALL_BIN) ./files/openthread-proto.sh $(1)/lib/netifd/proto/openthread.sh
+ $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/* $(1)/usr/sbin
+endef
+
+
+$(eval $(call BuildPackage,luci-app-openthread))
+$(eval $(call BuildPackage,openthread-br))
--- /dev/null
+# OpenThread Border Router
+
+This package contains the OpenThread Border Router.
+
+## Requirements
+
+To use this package, you need a Thread Radio Co-Processor (RCP). Testing of
+this package was done with Nordic Semiconductor nRF52840 USB dongles.
+
+Building and flashing the dongle with the Thread RCP firmware is out of scope
+of this document.
+
+One caveat for this dongle is worth mentioning here. The nRF52840 USB dongle
+seems to come with the U2F bootloader. To get it in mass storage mode to copy a
+firmware file, you need to plug it in while pressing the reset button. However,
+after the initial flash with the ot-rcp firmware, this method stops working.
+Instead, you need to double press the reset button after plugging in the dongle.
+
+## Packaging decisions
+
+### Configurable package build
+
+OpenThread is complex software. Adding config options to change the build of
+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
+
+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.
+
+This can be revised once the following feature request is implemented:
+https://github.com/openthread/ot-br-posix/issues/1675
+
+### mDNSResponder
+
+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.
+
+### REST Server
+
+The REST server is enabled to make this package compatible with Home Assistant.
+
+### TREL support
+
+Thread Radio Encapsulation Link support is enabled, as it allows Border Routers
+to communicate over other links (e.g. Ethernet), reducing traffic over the
+802.15.4 radios.
+
+The following Github discussion contains a good explanation of TREL:
+https://github.com/openthread/openthread/discussions/8478
+
+### UCI/netifd support
+
+The package contains a minimal netifd protocol handler. This allows configuring
+the Thread network in /etc/config/network. The agent will be started by netifd,
+rather than using an init script.
+
+OpenThread does not store prefix information in non-volatile storage. As a
+result, every time the agent is restarted, a different prefix would be used.
+This is not very nice, and makes it very difficult to run the OpenThread Border
+Router on a device that is not your main router. Therefore, prefixes can be
+configured in /etc/config/network. This way, you can add a static route to the
+Thread prefix(es) in your main router, making it possible to access devices on
+the Thread network from your entire network.
+
+## Create network
+
+When starting the OpenThread Border Router for the first time, a Thread network
+must be created.
+
+As the agent is started by netifd, we first need to create an interface in
+/etc/config/network:
+
+```
+config interface 'thread'
+ option device 'wpan0'
+ option proto 'openthread'
+ option backbone_network 'lan'
+ option radio_url 'spinel+hdlc+uart:///dev/ttyACM0?uart-baudrate=460800'
+ list prefix 'fd6f:5772:5468:7200::/64 paros'
+ 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:
+
+```
+# ifup thread
+# ifstatus thread
+{
+ "up": false,
+ "pending": false,
+ "available": true,
+ "autostart": false,
+ "dynamic": false,
+ "proto": "openthread",
+ "data": {
+
+ },
+ "errors": [
+ {
+ "subsystem": "openthread",
+ "code": "MISSING_BACKBONE_NETWORK"
+ }
+ ]
+}
+```
+
+In the above example, the backbone_network option is missing.
+
+The protocol handler will automatically start the the Thread network, so we
+need to bring it down for the initial setup. This only needs to be done once.
+
+```
+ubus call otbr threadstop
+```
+
+### LuCI
+
+Creating a network in LuCI appears to be broken for the moment.
+
+### CLI
+
+```
+ot-ctl dataset init new
+ot-ctl dataset panid 0x12ab
+ot-ctl dataset extpanid 12ab12ab12ab12ab
+ot-ctl dataset networkname OpenWrThread
+ot-ctl dataset networkkey ddf429af1c52d1735ffaf36fae343ee8
+ot-ctl dataset commit active
+ot-ctl ifconfig up
+ot-ctl thread start
+ot-ctl netdata register
+```
+
+### Configure route
+
+Before you can join a device to your new Thread network, you must add a route
+to the Thread prefix on the commissioner device via the OpenWrt router running
+the OpenThread Border Router.
+
+Get the prefix:
+```
+ot-ctl prefix
+```
+
+Example output:
+
+```
+fd6b:a92f:c531:1::/64 paros low f000
+Done
+```
+
+Configuring the route is out of scope of this document, but it must be done, or
+joining Thread devices will fail.
+
+### Get hex-encoded operational dataset TLV
+
+This is needed to join devices to the Thread Network.
+
+```
+ot-ctl dataset active -x
+```
+
+Example output:
+
+```
+0e080000000000010000000300001035060004001fffe00708fd488c6a892ec30c04106e220c964a14a7e10e9004691920ec390c0402a0f7f80102ffff030b5468726541646c6576696f0208ffffffffffffffff0510ddf429af1c52d1735ffaf36fae343ee8
+```
+
+## Join another OpenThread Border Router
+
+Simply configure the active dataset in /etc/config/network:
+
+```
+config interface 'thread'
+ option device 'wpan0'
+ option proto 'openthread'
+ option backbone_network 'lan'
+ option dataset '0e080000000000010000000300000f35060004001fffe0020836b86cd9746ab3080708fd9850cbe719b1d205101f11a11320828c7a6ebc2f2e675c0dca030e686f6d652d617373697374616e740102716f041025804ed78614258ebedf4e2db37b3b6e0c0402a0f7f8'
+ list prefix 'fd6f:5772:5468:7200::/64 paros'
+ option radio_url 'spinel+hdlc+uart:///dev/ttyACM0?uart-baudrate=460800'
+ option verbose '0'
+```
+
+Afterwards, bring up the interface:
+
+```
+ifup thread
+```
+
+## Join a Thread device via Matter
+
+### ESP32
+The following procedure has been tested with an ESP32-C6 using [the Matter
+lighting-app example](https://github.com/project-chip/connectedhomeip/tree/master/examples/lighting-app/esp32).
+Building and flashing that app is out of scope of this document.
+
+During startup, the lighting app will print the SetupQRCode to the serial
+console:
+
+```
+I (1614) chip[SVR]: SetupQRCode: [MT:Y.K9042C00KA0648G00]
+I (1624) chip[SVR]: Copy/paste the below URL in a browser to see the QR Code:
+I (1634) chip[SVR]: https://project-chip.github.io/connectedhomeip/qrcode.html?data=MT%3AY.K9042C00KA0648G00
+I (1644) chip[SVR]: Manual pairing code: [34970112332]
+```
+
+Decide on a node ID for the device.
+
+```
+./chip-tool pairing code-thread 0x65737933320000 hex:0e080000000000010000000300001035060004001fffe00708fd488c6a892ec30c04106e220c964a14a7e10e9004691920ec390c0402a0f7f80102ffff030b5468726541646c6576696f0208ffffffffffffffff0510ddf429af1c52d1735ffaf36fae343ee8 MT:Y.K9042C00KA0648G00 --paa-trust-store-path /path/to/connectedhomeip/credentials/test/attestation/
+```
+
--- /dev/null
+#!/bin/sh
+#
+# SPDX-FileCopyrightText: 2023 Stijn Tintel <stijn@linux-ipv6.be>
+# SPDX-License-Identifier: GPL-2.0-only
+
+OTCTL="/usr/sbin/ot-ctl"
+PROG="/usr/sbin/otbr-agent"
+
+[ -x "$PROG" ] || exit 0
+
+[ -n "$INCLUDE_ONLY" ] || {
+ . /lib/functions.sh
+ . /lib/functions/network.sh
+ . ../netifd-proto.sh
+ init_proto "$@"
+}
+
+proto_openthread_add_prefix() {
+ prefix="$1"
+ # shellcheck disable=SC2086
+ [ -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
+
+ available=1
+ no_device=1
+}
+
+proto_openthread_setup_error() {
+ interface="$1"
+ error="$2"
+ proto_notify_error "$interface" "$error"
+ # prevent netifd from trying to bring up interface over and over
+ proto_block_restart "$interface"
+ proto_setup_failed "$interface"
+ exit 1
+}
+
+proto_openthread_setup() {
+ interface="$1"
+ device="$2"
+
+ json_get_vars backbone_network dataset device radio_url verbose:0
+
+ [ -n "$backbone_network" ] || proto_openthread_setup_error "$interface" MISSING_BACKBONE_NETWORK
+ proto_add_host_dependency "$interface" "" "$backbone_network"
+ network_get_device backbone_ifname "$backbone_network"
+
+ [ -n "$backbone_ifname" ] || proto_openthread_setup_error "$interface" MISSING_BACKBONE_IFNAME
+ [ -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"
+ append opts "$radio_url"
+ append opts "trel://$backbone_ifname"
+ # run in subshell to prevent wiping json data needed for prefixes
+ ( proto_run_command "$interface" "$PROG" $opts )
+
+ ubus -t30 wait_for otbr
+
+ [ -n "$dataset" ] && {
+ $OTCTL dataset set active "$dataset"
+ }
+
+ json_for_each_item proto_openthread_add_prefix prefix
+ ubus call otbr threadstart || proto_openthread_setup_error "$interface" MISSING_UBUS_OBJ
+ $OTCTL netdata register
+
+ proto_init_update "$device" 1 1
+ proto_send_update "$interface"
+}
+
+proto_openthread_teardown() {
+ interface="$1"
+ ubus call otbr threadstop
+ proto_kill_command "$interface"
+}
+
+[ -n "$INCLUDE_ONLY" ] || {
+ add_protocol openthread
+}
--- /dev/null
+From d9086b843d5da519fca876794d14026b14cc68ae Mon Sep 17 00:00:00 2001
+Message-ID: <d9086b843d5da519fca876794d14026b14cc68ae.1689665371.git.stefan@agner.ch>
+From: Stefan Agner <stefan@agner.ch>
+Date: Mon, 5 Jun 2023 23:41:50 +0200
+Subject: [PATCH] [rest] support deleting the dataset
+
+Add REST API to support deleting the active or pending operational
+dataset. Deleting the active operational dataset requires the Thread
+network to be disabled (just like modifying the active operational
+dataset). Subsequent use of the PUT method allows to build entirly
+new datasets with values generated by the stack (through
+otDatasetCreateNewNetwork).
+---
+ src/rest/openapi.yaml | 21 +++++++++++++++++++++
+ src/rest/resource.cpp | 35 +++++++++++++++++++++++++++++++++++
+ src/rest/resource.hpp | 1 +
+ 3 files changed, 57 insertions(+)
+
+diff --git a/src/rest/openapi.yaml b/src/rest/openapi.yaml
+index 2ba2a4dd56..2edc4af29a 100644
+--- a/src/rest/openapi.yaml
++++ b/src/rest/openapi.yaml
+@@ -248,6 +248,18 @@ paths:
+ description: Invalid request body.
+ "409":
+ description: Writing active operational dataset rejected because Thread network is active.
++ delete:
++ tags:
++ - node
++ summary: Deletes the active operational dataset
++ description: |-
++ Deletes the the active operational dataset on the current node. Only allowed if the Thread node
++ is inactive.
++ responses:
++ "200":
++ description: Successfully deleted the active operational dataset.
++ "409":
++ description: Deleting active operational dataset rejected because Thread network is active.
+ /node/dataset/pending:
+ get:
+ tags:
+@@ -291,6 +303,15 @@ paths:
+ description: Successfully created the pending operational dataset.
+ "400":
+ description: Invalid request body.
++ delete:
++ tags:
++ - node
++ summary: Deletes the pending operational dataset
++ description: |-
++ Deletes the the pending operational dataset on the current node.
++ responses:
++ "200":
++ description: Successfully deleted the active operational dataset.
+ components:
+ schemas:
+ LeaderData:
+diff --git a/src/rest/resource.cpp b/src/rest/resource.cpp
+index a60e9d9483..829835341a 100644
+--- a/src/rest/resource.cpp
++++ b/src/rest/resource.cpp
+@@ -767,12 +767,47 @@ exit:
+ }
+ }
+
++void Resource::DeleteDataset(DatasetType aDatasetType, Response &aResponse) const
++{
++ otbrError error = OTBR_ERROR_NONE;
++ std::string errorCode = GetHttpStatus(HttpStatusCode::kStatusOk);
++ otOperationalDatasetTlvs datasetTlvs = {};
++
++ if (aDatasetType == DatasetType::kActive)
++ {
++ VerifyOrExit(otThreadGetDeviceRole(mInstance) == OT_DEVICE_ROLE_DISABLED, error = OTBR_ERROR_INVALID_STATE);
++ }
++
++ if (aDatasetType == DatasetType::kActive)
++ {
++ VerifyOrExit(otDatasetSetActiveTlvs(mInstance, &datasetTlvs) == OT_ERROR_NONE, error = OTBR_ERROR_REST);
++ }
++ else if (aDatasetType == DatasetType::kPending)
++ {
++ VerifyOrExit(otDatasetSetPendingTlvs(mInstance, &datasetTlvs) == OT_ERROR_NONE, error = OTBR_ERROR_REST);
++ }
++ aResponse.SetResponsCode(errorCode);
++
++exit:
++ if (error == OTBR_ERROR_INVALID_STATE)
++ {
++ ErrorHandler(aResponse, HttpStatusCode::kStatusConflict);
++ }
++ else if (error != OTBR_ERROR_NONE)
++ {
++ ErrorHandler(aResponse, HttpStatusCode::kStatusInternalServerError);
++ }
++}
++
+ void Resource::Dataset(DatasetType aDatasetType, const Request &aRequest, Response &aResponse) const
+ {
+ std::string errorCode;
+
+ switch (aRequest.GetMethod())
+ {
++ case HttpMethod::kDelete:
++ DeleteDataset(aDatasetType, aResponse);
++ break;
+ case HttpMethod::kGet:
+ GetDataset(aDatasetType, aRequest, aResponse);
+ break;
+diff --git a/src/rest/resource.hpp b/src/rest/resource.hpp
+index d79085dbfc..362e501471 100644
+--- a/src/rest/resource.hpp
++++ b/src/rest/resource.hpp
+@@ -150,6 +150,7 @@ private:
+ void GetDataRloc(Response &aResponse) const;
+ void GetDataset(DatasetType aDatasetType, const Request &aRequest, Response &aResponse) const;
+ void SetDataset(DatasetType aDatasetType, const Request &aRequest, Response &aResponse) const;
++ void DeleteDataset(DatasetType aDatasetType, Response &aResponse) const;
+
+ void DeleteOutDatedDiagnostic(void);
+ void UpdateDiag(std::string aKey, std::vector<otNetworkDiagTlv> &aDiag);
+--
+2.41.0
+