openthread-border-router: init at 0-unstable-2025-06-12 (#502388)
This commit is contained in:
@@ -46,6 +46,8 @@
|
||||
|
||||
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
|
||||
|
||||
- [OpenThread Border Router](https://openthread.io/), a Thread border router for POSIX-based platforms that bridges Thread mesh networks to IP networks. Available as [services.openthread-border-router](#opt-services.openthread-border-router.enable).
|
||||
|
||||
- [Meshtastic](https://meshtastic.org), an open-source, off-grid, decentralised mesh network
|
||||
designed to run on affordable, low-power devices. Available as [services.meshtasticd]
|
||||
(#opt-services.meshtasticd.enable).
|
||||
|
||||
@@ -729,6 +729,7 @@
|
||||
./services/home-automation/home-assistant.nix
|
||||
./services/home-automation/homebridge.nix
|
||||
./services/home-automation/matter-server.nix
|
||||
./services/home-automation/openthread-border-router.nix
|
||||
./services/home-automation/wyoming/faster-whisper.nix
|
||||
./services/home-automation/wyoming/openwakeword.nix
|
||||
./services/home-automation/wyoming/piper.nix
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
utils,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.openthread-border-router;
|
||||
logLevelMappings = {
|
||||
"emerg" = 0;
|
||||
"alert" = 1;
|
||||
"crit" = 2;
|
||||
"err" = 3;
|
||||
"warning" = 4;
|
||||
"notice" = 5;
|
||||
"info" = 6;
|
||||
"debug" = 7;
|
||||
};
|
||||
logLevel = lib.getAttr cfg.logLevel logLevelMappings;
|
||||
# Use correct iptables for otbr-firewall (legacy vs nf-compat)
|
||||
iptables =
|
||||
let
|
||||
inherit (config.networking) firewall;
|
||||
in
|
||||
if firewall.backend == "iptables" then firewall.package else pkgs.iptables;
|
||||
in
|
||||
{
|
||||
meta.maintainers = with lib.maintainers; [
|
||||
jamiemagee
|
||||
leonm1
|
||||
mrene
|
||||
];
|
||||
|
||||
options.services.openthread-border-router = {
|
||||
enable = lib.mkEnableOption "the OpenThread Border Router";
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to open the firewall for the REST API and web interface ports.";
|
||||
};
|
||||
|
||||
package = lib.mkPackageOption pkgs "openthread-border-router" { };
|
||||
|
||||
backboneInterfaces = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ "eth0" ];
|
||||
description = "The network interfaces on which to advertise the thread ipv6 mesh prefix. Can be specified multiple times.";
|
||||
};
|
||||
|
||||
interfaceName = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "wpan0";
|
||||
description = "The network interface to create for thread packets.";
|
||||
};
|
||||
|
||||
logLevel = lib.mkOption {
|
||||
type = lib.types.enum (lib.attrNames logLevelMappings);
|
||||
default = "err";
|
||||
description = "The level to use when logging messages.";
|
||||
};
|
||||
|
||||
rest = {
|
||||
listenAddress = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "127.0.0.1";
|
||||
description = "The address on which to listen for REST API requests.";
|
||||
example = "::";
|
||||
};
|
||||
|
||||
listenPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 8081;
|
||||
description = "The port on which to listen for REST API requests. Warning: the web interface relies on this value being set to 8081.";
|
||||
};
|
||||
};
|
||||
|
||||
web = {
|
||||
enable = lib.mkEnableOption "the web interface";
|
||||
listenAddress = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "127.0.0.1";
|
||||
description = "The address on which the web interface should listen.";
|
||||
example = "::";
|
||||
};
|
||||
|
||||
listenPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 8082;
|
||||
description = "The port on which the web interface should listen.";
|
||||
};
|
||||
};
|
||||
|
||||
radio = {
|
||||
device = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
The device name of the serial port of the radio device.
|
||||
Ignored if {option}`services.openthread-border-router.radio.url` is set.
|
||||
'';
|
||||
};
|
||||
|
||||
baudRate = lib.mkOption {
|
||||
type = lib.types.ints.positive;
|
||||
default = 115200;
|
||||
description = ''
|
||||
The baud rate of the radio device.
|
||||
Ignored if {option}`services.openthread-border-router.radio.url` is set.
|
||||
'';
|
||||
};
|
||||
|
||||
flowControl = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Enable hardware flow control.
|
||||
Ignored if {option}`services.openthread-border-router.radio.url` is set.
|
||||
'';
|
||||
};
|
||||
|
||||
urlQueryString = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
Extra URL query string parameters.
|
||||
Ignored if {option}`services.openthread-border-router.radio.url` is set.
|
||||
'';
|
||||
example = "bus-latency=100®ion=ca";
|
||||
};
|
||||
|
||||
url = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "The URL of the radio device to use.";
|
||||
example = "spinel+hdlc+uart:///dev/ttyUSB0?uart-baudrate=460800&uart-flow-control";
|
||||
};
|
||||
|
||||
extraDevices = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Extra devices to add to the radio device.";
|
||||
example = [ "trel://eth0" ];
|
||||
};
|
||||
};
|
||||
|
||||
extraArgs = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Extra arguments to pass to the otbr-agent daemon.";
|
||||
example = [ "--radio-version" ];
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.radio.device != null || cfg.radio.url != null;
|
||||
message = "services.openthread-border-router requires either radio.device or radio.url to be set.";
|
||||
}
|
||||
];
|
||||
|
||||
warnings = lib.optional (cfg.web.enable && cfg.rest.listenPort != 8081) ''
|
||||
The openthread-border-router web interface is hardcoded to talk to the REST API on port 8081, but its
|
||||
port has been changed to ${toString cfg.rest.listenPort}. Some features will be broken.
|
||||
'';
|
||||
|
||||
services.openthread-border-router.radio.url = lib.mkIf (cfg.radio.device != null) (
|
||||
lib.mkDefault (
|
||||
"spinel+hdlc+uart://${cfg.radio.device}?"
|
||||
+ lib.concatStringsSep "&" (
|
||||
[ "uart-baudrate=${toString cfg.radio.baudRate}" ]
|
||||
++ lib.optional cfg.radio.flowControl "uart-flow-control"
|
||||
++ lib.optional (cfg.radio.urlQueryString != "") cfg.radio.urlQueryString
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
# ot-ctl can be used to query the router instance
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
# Make sure we have ipv6 support, and that forwarding is enabled
|
||||
networking.enableIPv6 = true;
|
||||
networking.firewall.allowedTCPPorts =
|
||||
lib.optional cfg.openFirewall cfg.rest.listenPort
|
||||
++ lib.optional (cfg.openFirewall && cfg.web.enable) cfg.web.listenPort;
|
||||
boot.kernel.sysctl = {
|
||||
"net.ipv4.conf.all.forwarding" = 1;
|
||||
"net.ipv6.conf.all.forwarding" = 1;
|
||||
}
|
||||
// lib.listToAttrs (
|
||||
lib.concatMap (iface: [
|
||||
{
|
||||
name = "net.ipv6.conf.${iface}.accept_ra";
|
||||
value = 2;
|
||||
}
|
||||
{
|
||||
name = "net.ipv6.conf.${iface}.accept_ra_rt_info_max_plen";
|
||||
value = 64;
|
||||
}
|
||||
]) cfg.backboneInterfaces
|
||||
);
|
||||
|
||||
# OTBR uses avahi for mDNS service publishing
|
||||
services.avahi = {
|
||||
enable = lib.mkDefault true;
|
||||
publish = {
|
||||
enable = lib.mkDefault true;
|
||||
userServices = lib.mkDefault true;
|
||||
};
|
||||
};
|
||||
|
||||
# The upstream service files (src/agent/otbr-agent.service.in, src/web/otbr-web.service.in) use
|
||||
# EnvironmentFile and CMake-substituted platform scripts that don't translate to NixOS, so the
|
||||
# services are rebuilt here from typed module options instead.
|
||||
systemd.services = {
|
||||
# The agent keeps its local state in /var/lib/thread
|
||||
otbr-agent = {
|
||||
description = "OpenThread Border Router Agent";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
requires = [ "network-online.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
environment = {
|
||||
THREAD_IF = cfg.interfaceName;
|
||||
};
|
||||
serviceConfig = {
|
||||
ExecStartPre = "${utils.escapeSystemdExecArg (lib.getExe' cfg.package "otbr-firewall")} start";
|
||||
ExecStart = lib.concatStringsSep " " (
|
||||
lib.concatLists [
|
||||
[
|
||||
(lib.getExe' cfg.package "otbr-agent")
|
||||
"--verbose"
|
||||
]
|
||||
(map (iface: "--backbone-ifname ${utils.escapeSystemdExecArg iface}") cfg.backboneInterfaces)
|
||||
[
|
||||
"--thread-ifname ${utils.escapeSystemdExecArg cfg.interfaceName}"
|
||||
"--debug-level ${toString logLevel}"
|
||||
]
|
||||
(lib.optional (cfg.rest.listenPort != 0) "--rest-listen-port ${toString cfg.rest.listenPort}")
|
||||
(lib.optional (
|
||||
cfg.rest.listenAddress != ""
|
||||
) "--rest-listen-address ${utils.escapeSystemdExecArg cfg.rest.listenAddress}")
|
||||
(lib.optional (cfg.radio.url != null) (utils.escapeSystemdExecArg cfg.radio.url))
|
||||
(map utils.escapeSystemdExecArg cfg.radio.extraDevices)
|
||||
(map utils.escapeSystemdExecArg cfg.extraArgs)
|
||||
]
|
||||
);
|
||||
ExecStopPost = "${utils.escapeSystemdExecArg (lib.getExe' cfg.package "otbr-firewall")} stop";
|
||||
KillMode = "mixed";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 5;
|
||||
RestartPreventExitStatus = "SIGKILL";
|
||||
|
||||
# Hardening options (not present in upstream service definitions)
|
||||
StateDirectory = "thread";
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
ProtectClock = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectProc = "invisible";
|
||||
ProcSubset = "pid";
|
||||
NoNewPrivileges = true;
|
||||
LockPersonality = true;
|
||||
RestrictAddressFamilies = [
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
"AF_NETLINK"
|
||||
"AF_UNIX"
|
||||
];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallArchitectures = "native";
|
||||
UMask = "0077";
|
||||
|
||||
CapabilityBoundingSet = [
|
||||
"CAP_NET_ADMIN"
|
||||
"CAP_NET_RAW"
|
||||
];
|
||||
};
|
||||
path = [
|
||||
pkgs.ipset
|
||||
iptables
|
||||
];
|
||||
};
|
||||
|
||||
# Sync with: src/web/otbr-web.service.in
|
||||
otbr-web = lib.mkIf cfg.web.enable {
|
||||
description = "OpenThread Border Router Web Interface";
|
||||
after = [ "otbr-agent.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = lib.concatStringsSep " " (
|
||||
lib.concatLists [
|
||||
[
|
||||
(lib.getExe' cfg.package "otbr-web")
|
||||
"-I"
|
||||
(utils.escapeSystemdExecArg cfg.interfaceName)
|
||||
"-d"
|
||||
(toString logLevel)
|
||||
]
|
||||
(lib.optional (
|
||||
cfg.web.listenAddress != ""
|
||||
) "-a ${utils.escapeSystemdExecArg cfg.web.listenAddress}")
|
||||
(lib.optional (cfg.web.listenPort != 0) "-p ${toString cfg.web.listenPort}")
|
||||
]
|
||||
);
|
||||
|
||||
# Hardening options (not present in upstream service definitions)
|
||||
DynamicUser = true;
|
||||
PrivateUsers = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
ProtectClock = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectProc = "invisible";
|
||||
ProcSubset = "pid";
|
||||
NoNewPrivileges = true;
|
||||
LockPersonality = true;
|
||||
RestrictAddressFamilies = [
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
"AF_UNIX"
|
||||
];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallArchitectures = "native";
|
||||
UMask = "0077";
|
||||
CapabilityBoundingSet = "";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
diff --git a/script/otbr-firewall b/script/otbr-firewall
|
||||
--- a/script/otbr-firewall
|
||||
+++ b/script/otbr-firewall
|
||||
@@ -38,12 +38,8 @@
|
||||
# Description: This service sets up firewall for OTBR.
|
||||
### END INIT INFO
|
||||
|
||||
-THREAD_IF="wpan0"
|
||||
OTBR_FORWARD_INGRESS_CHAIN="OTBR_FORWARD_INGRESS"
|
||||
|
||||
-. /lib/lsb/init-functions
|
||||
-. /lib/init/vars.sh
|
||||
-
|
||||
set -euxo pipefail
|
||||
|
||||
ipset_destroy_if_exist()
|
||||
@@ -0,0 +1,122 @@
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
fetchFromGitHub,
|
||||
cmake,
|
||||
pkg-config,
|
||||
systemdLibs,
|
||||
avahi,
|
||||
dbus,
|
||||
protobuf,
|
||||
jsoncpp,
|
||||
boost,
|
||||
libnetfilter_queue,
|
||||
libnfnetlink,
|
||||
nodejs,
|
||||
bashNonInteractive,
|
||||
buildNpmPackage,
|
||||
}:
|
||||
let
|
||||
pname = "ot-br-posix";
|
||||
version = "0-unstable-2025-06-12";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "openthread";
|
||||
repo = "ot-br-posix";
|
||||
rev = "thread-reference-20250612";
|
||||
hash = "sha256-lPMMLtbPu9NpDcBCZE6XID7u1maCAhkZiSDEyFq7yvg=";
|
||||
fetchSubmodules = true;
|
||||
};
|
||||
|
||||
frontendModules = buildNpmPackage {
|
||||
pname = "${pname}-frontend";
|
||||
inherit version;
|
||||
src = "${src}/src/web/web-service/frontend";
|
||||
npmDepsHash = "sha256-7UVfPICyIbHEClpr3p7eDR46OUzS8mVf6P7phnDpVLk=";
|
||||
dontNpmBuild = true;
|
||||
};
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
inherit pname version src;
|
||||
|
||||
strictDeps = true;
|
||||
__structuredAttrs = true;
|
||||
|
||||
# warning _FORTIFY_SOURCE requires compiling with optimization (-O)
|
||||
env.NIX_CFLAGS_COMPILE = "-O";
|
||||
|
||||
patches = [
|
||||
# Patch the firewall script so we can run it within the systemd start script
|
||||
./firewall-script.patch
|
||||
];
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkg-config
|
||||
cmake
|
||||
nodejs
|
||||
];
|
||||
|
||||
# Adding npmConfigHook and manually passing fetchNpmDeps was resulting in ENOTCACHED errors
|
||||
postConfigure = ''
|
||||
ln -sf ${frontendModules}/lib/node_modules/otbr-web/node_modules ./src/web/web-service/frontend/
|
||||
'';
|
||||
|
||||
buildInputs = [
|
||||
avahi # TODO: upstream deprecated OTBR_MDNS=avahi after this release (https://github.com/openthread/ot-br-posix/pull/3240)
|
||||
systemdLibs
|
||||
protobuf
|
||||
jsoncpp
|
||||
boost
|
||||
libnetfilter_queue
|
||||
libnfnetlink
|
||||
dbus
|
||||
(lib.getBin bashNonInteractive)
|
||||
];
|
||||
|
||||
postInstall = ''
|
||||
mkdir -p $out/bin
|
||||
cp ../script/otbr-firewall $out/bin/
|
||||
chmod +x $out/bin/otbr-firewall
|
||||
'';
|
||||
|
||||
cmakeFlags = [
|
||||
(lib.cmakeFeature "CMAKE_POLICY_VERSION_MINIMUM" "3.5")
|
||||
|
||||
(lib.cmakeBool "BUILD_TESTING" false)
|
||||
(lib.cmakeBool "INSTALL_SYSTEMD_UNIT" false)
|
||||
(lib.cmakeBool "Boost_USE_STATIC_LIBS" false)
|
||||
(lib.cmakeBool "OTBR_REST" true)
|
||||
|
||||
(lib.cmakeBool "OTBR_WEB" true)
|
||||
(lib.cmakeBool "OTBR_NAT64" true)
|
||||
(lib.cmakeBool "OTBR_BACKBONE_ROUTER" true)
|
||||
(lib.cmakeBool "OTBR_BORDER_ROUTING" true)
|
||||
(lib.cmakeBool "OTBR_DBUS" true)
|
||||
(lib.cmakeBool "OTBR_TREL" true)
|
||||
|
||||
(lib.cmakeFeature "OTBR_VERSION" version)
|
||||
(lib.cmakeBool "OTBR_DNSSD_DISCOVERY_PROXY" true)
|
||||
(lib.cmakeBool "OTBR_SRP_ADVERTISING_PROXY" true)
|
||||
(lib.cmakeBool "OTBR_DUA_ROUTING" true)
|
||||
(lib.cmakeBool "OTBR_DNS_UPSTREAM_QUERY" true)
|
||||
|
||||
(lib.cmakeBool "OT_CHANNEL_MANAGER" true)
|
||||
(lib.cmakeBool "OT_CHANNEL_MONITOR" true)
|
||||
|
||||
# Required by protobuf
|
||||
(lib.cmakeFeature "CMAKE_CXX_STANDARD" "17")
|
||||
];
|
||||
|
||||
meta = {
|
||||
description = "Thread border router for POSIX-based platforms";
|
||||
homepage = "https://github.com/openthread/ot-br-posix";
|
||||
license = lib.licenses.bsd3;
|
||||
maintainers = with lib.maintainers; [
|
||||
jamiemagee
|
||||
leonm1
|
||||
mrene
|
||||
];
|
||||
mainProgram = "ot-ctl";
|
||||
platforms = lib.platforms.linux;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user