From 711950cd3616bbfd6cd47b974c48a385e7d046e5 Mon Sep 17 00:00:00 2001 From: Stijn Tintel Date: Sun, 21 Aug 2022 20:16:02 +0300 Subject: [PATCH] openthread-br: new package Add a new package for the OpenThread Border Router. Comes with a netifd protocol handler. See README.md for more information. Signed-off-by: Stijn Tintel --- net/openthread-br/Makefile | 107 +++++++++ net/openthread-br/README.md | 222 ++++++++++++++++++ net/openthread-br/files/openthread-proto.sh | 106 +++++++++ ...00-rest-support-deleting-the-dataset.patch | 124 ++++++++++ 4 files changed, 559 insertions(+) create mode 100644 net/openthread-br/Makefile create mode 100644 net/openthread-br/README.md create mode 100644 net/openthread-br/files/openthread-proto.sh create mode 100644 net/openthread-br/patches/100-rest-support-deleting-the-dataset.patch diff --git a/net/openthread-br/Makefile b/net/openthread-br/Makefile new file mode 100644 index 0000000000..1b42cceee5 --- /dev/null +++ b/net/openthread-br/Makefile @@ -0,0 +1,107 @@ +# SPDX-FileCopyrightText: 2022-2023 Stijn Tintel +# 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 +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)) diff --git a/net/openthread-br/README.md b/net/openthread-br/README.md new file mode 100644 index 0000000000..e4a26d427b --- /dev/null +++ b/net/openthread-br/README.md @@ -0,0 +1,222 @@ +# 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/ +``` + diff --git a/net/openthread-br/files/openthread-proto.sh b/net/openthread-br/files/openthread-proto.sh new file mode 100644 index 0000000000..928bafe712 --- /dev/null +++ b/net/openthread-br/files/openthread-proto.sh @@ -0,0 +1,106 @@ +#!/bin/sh +# +# SPDX-FileCopyrightText: 2023 Stijn Tintel +# 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 +} diff --git a/net/openthread-br/patches/100-rest-support-deleting-the-dataset.patch b/net/openthread-br/patches/100-rest-support-deleting-the-dataset.patch new file mode 100644 index 0000000000..014c306f3b --- /dev/null +++ b/net/openthread-br/patches/100-rest-support-deleting-the-dataset.patch @@ -0,0 +1,124 @@ +From d9086b843d5da519fca876794d14026b14cc68ae Mon Sep 17 00:00:00 2001 +Message-ID: +From: Stefan Agner +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 &aDiag); +-- +2.41.0 + -- 2.30.2