From: Felix Fietkau Date: Fri, 19 Jun 2026 14:32:19 +0000 (+0200) Subject: base-files: derive DHCP DUID from device MAC address X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=8c613a9a224c0d2c096fc635980834b9954db035;p=thirdparty%2Fopenwrt.git base-files: derive DHCP DUID from device MAC address The DUID-UUID was generated from a random UUID, which is regenerated on every fresh flash and therefore does not survive reconfiguration events such as reflashing without keeping the configuration. RFC8415/RFC6355 prefer a DUID that remains stable across such events. Add a ucode helper that picks the first LAN port (falling back to WAN) from /etc/board.json, resolves its MAC address and derives a stable UUID from it, falling back to a random UUID only when that fails. Signed-off-by: Felix Fietkau --- diff --git a/package/base-files/Makefile b/package/base-files/Makefile index 2f84179cb30..d19f6fc7124 100644 --- a/package/base-files/Makefile +++ b/package/base-files/Makefile @@ -43,7 +43,8 @@ define Package/base-files +netifd +libc +jsonfilter +SIGNED_PACKAGES:usign +SIGNED_PACKAGES:openwrt-keyring \ +NAND_SUPPORT:ubi-utils +fstools +fwtool \ +SELINUX:procd-selinux +!SELINUX:procd +USE_SECCOMP:procd-seccomp \ - +SELINUX:busybox-selinux +!SELINUX:busybox + +SELINUX:busybox-selinux +!SELINUX:busybox \ + +ucode +ucode-mod-fs +ucode-mod-digest TITLE:=Base filesystem for OpenWrt URL:=https://openwrt.org/ VERSION:=$(PKG_RELEASE)~$(lastword $(subst -, ,$(REVISION))) diff --git a/package/base-files/files/etc/uci-defaults/14_network-generate-duid b/package/base-files/files/etc/uci-defaults/14_network-generate-duid index c793dba6959..77428976175 100644 --- a/package/base-files/files/etc/uci-defaults/14_network-generate-duid +++ b/package/base-files/files/etc/uci-defaults/14_network-generate-duid @@ -1,8 +1,10 @@ [ "$(uci -q get network.globals.dhcp_default_duid || echo "auto")" != "auto" ] && exit 0 +duid="$(/usr/libexec/duid-generate)" +[ -n "$duid" ] || exit 1 + uci -q batch <<-EOF >/dev/null - # DUID-UUID - RFC6355 - set network.globals.dhcp_default_duid="$(printf '%s%s' '0004' $(cat /proc/sys/kernel/random/uuid | sed -e 's/-//g'))" + set network.globals.dhcp_default_duid="$duid" commit network EOF diff --git a/package/base-files/files/usr/libexec/duid-generate b/package/base-files/files/usr/libexec/duid-generate new file mode 100755 index 00000000000..2318239feb7 --- /dev/null +++ b/package/base-files/files/usr/libexec/duid-generate @@ -0,0 +1,120 @@ +#!/usr/bin/env ucode +'use strict'; + +import { readfile } from 'fs'; +import * as digest from 'digest'; + +// Fixed salt prefixed to the MAC address before hashing. It only separates this +// particular use of the MAC from any other; the value itself carries no meaning. +const DUID_SALT = 'openwrt-dhcp-duid:'; +const DUID_TYPE_UUID = '0004'; + +function mac_normalise(mac) { + if (type(mac) != 'string') + return null; + + mac = lc(trim(mac)); + + if (!match(mac, /^[0-9a-f]{2}(:[0-9a-f]{2}){5}$/)) + return null; + + if (mac == '00:00:00:00:00:00') + return null; + + return mac; +} + +function sysfs_mac(dev) { + if (!dev) + return null; + + return mac_normalise(readfile('/sys/class/net/' + dev + '/address')); +} + +function role_mac(board, role) { + if (type(role) != 'object') + return null; + + let dev; + if (type(role.ports) == 'array' && length(role.ports) > 0) + dev = role.ports[0]; + else + dev = role.device; + + // Prefer the live hardware address of the physical port. + let mac = sysfs_mac(dev); + if (mac) + return mac; + + // Fall back to MAC addresses recorded in board.json. + mac = mac_normalise(board?.network_device?.[dev]?.macaddr); + if (mac) + return mac; + + return mac_normalise(role.macaddr); +} + +function board_mac(board) { + let net = board?.network; + if (type(net) != 'object') + return null; + + for (let prefix in ['lan', 'wan']) { + let mac = role_mac(board, net[prefix]); + if (mac) + return mac; + + for (let name in keys(net)) { + if (substr(name, 0, length(prefix)) != prefix) + continue; + + mac = role_mac(board, net[name]); + if (mac) + return mac; + } + } + + return null; +} + +function uuid_set_bits(u, version) { + let b6 = (hex(substr(u, 12, 2)) & 0x0f) | (version << 4); + let b8 = (hex(substr(u, 16, 2)) & 0x3f) | 0x80; + + return substr(u, 0, 12) + sprintf('%02x', b6) + + substr(u, 14, 2) + sprintf('%02x', b8) + + substr(u, 18, 14); +} + +function uuid_from_mac(mac) { + let hash = digest.sha1(DUID_SALT + mac); + if (type(hash) != 'string' || length(hash) < 32) + return null; + + return uuid_set_bits(substr(hash, 0, 32), 5); +} + +function uuid_random() { + let uuid = readfile('/proc/sys/kernel/random/uuid'); + if (!uuid) + return null; + + return join('', split(trim(uuid), '-')); +} + +let board; +try { + board = json(readfile('/etc/board.json')); +} catch (e) { + board = null; +} + +let mac = board_mac(board); +let uuid = mac ? uuid_from_mac(mac) : null; + +uuid ??= uuid_random(); + +if (!uuid) + exit(1); + +print(DUID_TYPE_UUID + uuid + '\n');