From 4b55e3874126c5403011c6b9098dfaf0975d7cfa Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 30 Oct 2025 10:09:56 +0100 Subject: [PATCH] board/aarch64: use %m modifier in default xPi hostnames The xPi's usually don't have a VPD so the chassis mac-address probed at boot is usually null in /run/system.json. This commit adds a fallbkack mechanism to populate this field so it can be used for unique hostnames even on these boards. Ths ietf-hardware.yang model does not have a notion of physical address, so we augment one tht is generic enought to be used for other hardware components than Ethernet, similar to what ietf-interfaces.yang use. Signed-off-by: Joachim Wiberg --- .../bananapi,bpi-r3/etc/factory-config.cfg | 2 +- .../etc/factory-config.cfg | 2 +- .../etc/factory-config.cfg | 2 +- .../rootfs/usr/libexec/infix/init.d/00-probe | 47 +++++++++++++++++-- src/confd/yang/confd.inc | 2 +- src/confd/yang/confd/infix-hardware.yang | 16 ++++++- ...18.yang => infix-hardware@2025-10-30.yang} | 0 src/statd/python/cli_pretty/cli_pretty.py | 2 + src/statd/python/yanger/ietf_hardware.py | 4 ++ test/case/statd/system/ietf-hardware.json | 1 + 10 files changed, 67 insertions(+), 11 deletions(-) rename src/confd/yang/confd/{infix-hardware@2025-10-18.yang => infix-hardware@2025-10-30.yang} (100%) diff --git a/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/factory-config.cfg b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/factory-config.cfg index 8d94c11b..aa8740c3 100644 --- a/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/factory-config.cfg +++ b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/factory-config.cfg @@ -199,7 +199,7 @@ } }, "ietf-system:system": { - "hostname": "bpi-r3", + "hostname": "bpi-%m", "ntp": { "server": [ { diff --git a/board/aarch64/friendlyarm-nanopi-r2s/rootfs/usr/share/product/friendlyarm,nanopi-r2s/etc/factory-config.cfg b/board/aarch64/friendlyarm-nanopi-r2s/rootfs/usr/share/product/friendlyarm,nanopi-r2s/etc/factory-config.cfg index eca53da5..f4511ef1 100644 --- a/board/aarch64/friendlyarm-nanopi-r2s/rootfs/usr/share/product/friendlyarm,nanopi-r2s/etc/factory-config.cfg +++ b/board/aarch64/friendlyarm-nanopi-r2s/rootfs/usr/share/product/friendlyarm,nanopi-r2s/etc/factory-config.cfg @@ -150,7 +150,7 @@ } }, "ietf-system:system": { - "hostname": "r2s", + "hostname": "r2s-%m", "ntp": { "server": [ { diff --git a/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,4-model-b/etc/factory-config.cfg b/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,4-model-b/etc/factory-config.cfg index c744b02a..42ae38cc 100644 --- a/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,4-model-b/etc/factory-config.cfg +++ b/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,4-model-b/etc/factory-config.cfg @@ -133,7 +133,7 @@ } }, "ietf-system:system": { - "hostname": "rpi", + "hostname": "rpi-%m", "ntp": { "enabled": true, "server": [ diff --git a/board/common/rootfs/usr/libexec/infix/init.d/00-probe b/board/common/rootfs/usr/libexec/infix/init.d/00-probe index 2e4a7412..618f4ba8 100755 --- a/board/common/rootfs/usr/libexec/infix/init.d/00-probe +++ b/board/common/rootfs/usr/libexec/infix/init.d/00-probe @@ -277,8 +277,12 @@ def vpd_inject(out, vpds): break -def qemu_base_mac(): - """Find MAC address of first non-loopback interface, subtract with 1""" +def fallback_base_mac(): + """Find MAC address of first suitable non-loopback interface. + + Prefers real (globally unique) MACs over locally administered ones. + Prioritizes interface types: eth* > wan > wifi* > others. + """ base_path = '/sys/class/net' interfaces = [] @@ -290,13 +294,41 @@ def qemu_base_mac(): fn = os.path.join(base_path, iface, 'address') with open(fn, 'r', encoding='ascii') as f: mac = f.read().strip() - interfaces.append((mac, iface)) + + # Check if locally administered (bit 1 of first octet is set) + first_byte = int(mac.split(':')[0], 16) + is_local = bool(first_byte & 0x02) + + # Prefer: eth* > wan > wifi* > others, then real MACs > local MACs + priority = 0 + if iface.startswith('eth'): + priority = 400 + elif iface == 'wan': + priority = 300 + elif iface.startswith('wifi'): + priority = 200 + else: + priority = 100 + + # Real MACs get +100 bonus + if not is_local: + priority += 100 + + interfaces.append((priority, iface, mac)) except FileNotFoundError: continue if interfaces: - interfaces.sort() - mac = interfaces[0][0] + interfaces.sort(reverse=True) # Highest priority first + return interfaces[0][2] # Return MAC + + return None + + +def qemu_base_mac(): + """Find MAC address of first non-loopback interface, subtract with 1""" + mac = fallback_base_mac() + if mac: mac = int(mac.replace(':', ''), 16) mac -= 1 mac %= 1 << 48 @@ -386,6 +418,11 @@ def probe_dtsystem(out): out["factory-password-hash"] = staticpw vpd_inject(out, vpds) + + # Fallback to interface MAC if VPD doesn't provide one (e.g., SBCs) + if not out["mac-address"]: + out["mac-address"] = fallback_base_mac() + return 0 diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index f15ac271..03b03a70 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -18,7 +18,7 @@ MODULES=( "infix-syslog@2024-07-19.yang" "iana-hardware@2018-03-13.yang" "ietf-hardware@2018-03-13.yang -e hardware-state -e hardware-sensor" - "infix-hardware@2025-10-18.yang" + "infix-hardware@2025-10-30.yang" "ieee802-dot1q-types@2022-10-29.yang" "infix-ip@2024-09-16.yang" "infix-if-type@2025-02-12.yang" diff --git a/src/confd/yang/confd/infix-hardware.yang b/src/confd/yang/confd/infix-hardware.yang index 535b1b3d..d702994f 100644 --- a/src/confd/yang/confd/infix-hardware.yang +++ b/src/confd/yang/confd/infix-hardware.yang @@ -17,8 +17,8 @@ module infix-hardware { contact "kernelkit@googlegroups.com"; description "Vital Product Data augmentation of ieee-hardware and deviations."; - revision 2025-10-18 { - description "Enable sensor support, starting with hwmon temperature sensors."; + revision 2025-10-30 { + description "Add phys-address leaf for hardware components and enable sensor support."; reference "internal"; } revision 2024-04-25 { @@ -72,6 +72,18 @@ module infix-hardware { deviate not-supported; } augment "/iehw:hardware/iehw:component" { + leaf phys-address { + type yang:phys-address; + config false; + description + "The physical address of the hardware component. For chassis components, + this represents the base MAC address used for the system. This is + typically sourced from VPD data on enterprise hardware, or derived from + the first physical interface on single-board computers (SBCs) without + VPD. May be used as the base for generating addresses for virtual + interfaces. For other component types, this could represent various + physical layer addresses (e.g., Fibre Channel WWN, InfiniBand GUID)."; + } container vpd-data { config false; leaf product-name { diff --git a/src/confd/yang/confd/infix-hardware@2025-10-18.yang b/src/confd/yang/confd/infix-hardware@2025-10-30.yang similarity index 100% rename from src/confd/yang/confd/infix-hardware@2025-10-18.yang rename to src/confd/yang/confd/infix-hardware@2025-10-30.yang diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index 9ffee87f..a364db97 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -1703,6 +1703,8 @@ def show_hardware(json): print(f"Manufacturer : {board['mfg-name']}") if board.get("serial-num"): print(f"Serial Number : {board['serial-num']}") + if board.get("infix-hardware:phys-address"): + print(f"Base MAC Address : {board['infix-hardware:phys-address']}") if board.get("hardware-rev"): print(f"Hardware Revision : {board['hardware-rev']}") diff --git a/src/statd/python/yanger/ietf_hardware.py b/src/statd/python/yanger/ietf_hardware.py index 1cbf15a9..3535cbc6 100644 --- a/src/statd/python/yanger/ietf_hardware.py +++ b/src/statd/python/yanger/ietf_hardware.py @@ -99,6 +99,10 @@ def motherboard_component(systemjson): if systemjson.get("part-number"): component["hardware-rev"] = systemjson["part-number"] + # Add chassis physical address (MAC) if available (from VPD or interface fallback) + if systemjson.get("mac-address"): + component["infix-hardware:phys-address"] = systemjson["mac-address"] + # Set state - admin-state is "unknown" since chassis cannot be # administratively controlled (locked/unlocked) component["state"] = { diff --git a/test/case/statd/system/ietf-hardware.json b/test/case/statd/system/ietf-hardware.json index 4ced153b..637b5c3e 100644 --- a/test/case/statd/system/ietf-hardware.json +++ b/test/case/statd/system/ietf-hardware.json @@ -6,6 +6,7 @@ "class": "iana-hardware:chassis", "mfg-name": "QEMU", "model-name": "VM", + "infix-hardware:phys-address": "00:a0:85:00:03:00", "state": { "admin-state": "unknown", "oper-state": "enabled"