From 90f619bfa660b026e5d49d9a4270e39d26f209bc Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Fri, 14 Nov 2025 10:42:54 +0100 Subject: [PATCH] confd: add deterministic hostname management via /etc/hostname.d/ Resolve race condition between DHCP and configured hostname by introducing priority-based hostname management using /etc/hostname.d/ directory pattern. Priority order (highest wins): 90-dhcp- - DHCP assigned hostname 50-configured - YANG /system/hostname config 10-default - Bootstrap/factory default The new /usr/libexec/infix/hostname helper reads all sources and applies the highest priority hostname. It exits early if hostname unchanged, preventing unnecessary service restarts. Fixes #1112 Signed-off-by: Joachim Wiberg --- .../common/rootfs/usr/libexec/infix/hostname | 74 +++++++++++++++ .../usr/libexec/infix/init.d/05-hostname | 18 ++++ .../rootfs/usr/share/udhcpc/default.script | 11 ++- src/confd/src/ietf-system.c | 95 +++++-------------- 4 files changed, 122 insertions(+), 76 deletions(-) create mode 100755 board/common/rootfs/usr/libexec/infix/hostname create mode 100755 board/common/rootfs/usr/libexec/infix/init.d/05-hostname diff --git a/board/common/rootfs/usr/libexec/infix/hostname b/board/common/rootfs/usr/libexec/infix/hostname new file mode 100755 index 00000000..bfd1952b --- /dev/null +++ b/board/common/rootfs/usr/libexec/infix/hostname @@ -0,0 +1,74 @@ +#!/bin/sh +# Deterministically set system hostname from /etc/hostname.d/ +# +# Highest numbered file wins (lexicographic sort, 90-dhcp > 50-configured > 10-default) +# +# Priority scheme: +# 10-default - Bootstrap/factory default (%h-%m format) +# 50-configured - From confd /system/hostname +# 90-dhcp- - From DHCP clietn on interface (highest priority) + +HOSTNAME_D="/etc/hostname.d" + +# Ensure directory exists +mkdir -p "$HOSTNAME_D" + +# Find the highest priority file (reverse sort, take first) +hostname_file=$(ls -1 "$HOSTNAME_D" 2>/dev/null | sort -r | head -1) + +if [ -z "$hostname_file" ]; then + logger -it confd "No hostname sources found in $HOSTNAME_D" + exit 1 +fi + +# Read hostname from the file (first line only, strip whitespace) +new_hostname=$(cat "$HOSTNAME_D/$hostname_file" | head -1 | tr -d '\n\r\t ') +if [ -z "$new_hostname" ]; then + logger -it confd "Empty hostname in $hostname_file" + exit 1 +fi + +if [ ${#new_hostname} -gt 64 ]; then + logger -it confd "Hostname too long (${#new_hostname} > 64) in $hostname_file" + exit 1 +fi + +# Check if hostname has actually changed +current_hostname=$(hostname) +if [ "$new_hostname" = "$current_hostname" ]; then + # No change needed, exit silently + exit 0 +fi + +# Set the hostname +logger -it confd "Setting hostname to '$new_hostname' from $hostname_file" +hostname "$new_hostname" + +# Update /etc/hostname (for persistence across reboots) +echo "$new_hostname" > /etc/hostname + +# Update /etc/hosts (127.0.1.1 entry for proper name resolution) +if grep -q "^127\.0\.1\.1" /etc/hosts; then + sed -i -E "s/^(127\.0\.1\.1\s+).*/\1$new_hostname/" /etc/hosts +else + # Add entry if it doesn't exist + echo "127.0.1.1 $new_hostname" >> /etc/hosts +fi + +# Notify services of hostname change, skip while in bootstrap +initctl -nbq touch sysklogd +if ! runlevel >/dev/null 2>&1; then + exit 0 +fi + +initctl -bq status lldpd && lldpcli configure system hostname "$new_hostname" 2>/dev/null +initctl -bq status mdns && avahi-set-host-name "$new_hostname" 2>/dev/null +initctl -bq touch netbrowse 2>/dev/null + +# If called from dhcp script we need to reload to activate new name in syslogd +# Otherwise we're called from confd, which does the reload when all is done. +if [ -n "$1" ]; then + initctl -b reload +fi + +exit 0 diff --git a/board/common/rootfs/usr/libexec/infix/init.d/05-hostname b/board/common/rootfs/usr/libexec/infix/init.d/05-hostname new file mode 100755 index 00000000..2984ea3c --- /dev/null +++ b/board/common/rootfs/usr/libexec/infix/init.d/05-hostname @@ -0,0 +1,18 @@ +#!/bin/sh +# Initialize default hostname for hostname.d pattern +# This runs very early in boot to set up the default hostname entry + +HOSTNAME_D="/etc/hostname.d" + +# Ensure directory exists +mkdir -p "$HOSTNAME_D" + +# If no default exists yet, create it from /etc/hostname (from squashfs) +if [ ! -f "$HOSTNAME_D/10-default" ] && [ -f /etc/hostname ]; then + cp /etc/hostname "$HOSTNAME_D/10-default" +fi + +# Apply hostname using the deterministic helper +if [ -x /usr/libexec/infix/hostname ]; then + /usr/libexec/infix/hostname +fi diff --git a/board/common/rootfs/usr/share/udhcpc/default.script b/board/common/rootfs/usr/share/udhcpc/default.script index 5fb87935..d4f58e55 100755 --- a/board/common/rootfs/usr/share/udhcpc/default.script +++ b/board/common/rootfs/usr/share/udhcpc/default.script @@ -108,6 +108,10 @@ case "$ACTION" in # drop info from this interface rm -f "$RESOLV_CONF" rm -f "$NTPFILE" + if [ -f "/etc/hostname.d/90-dhcp-${interface}" ]; then + rm -f "/etc/hostname.d/90-dhcp-${interface}" + /usr/libexec/infix/hostname dhcp + fi if [ -x /usr/sbin/avahi-autoipd ]; then /usr/sbin/avahi-autoipd -c $interface && /usr/sbin/avahi-autoipd -k $interface fi @@ -135,9 +139,10 @@ case "$ACTION" in # set hostname if given if [ -n "$hostname" ]; then - log "setting new hostname: $hostname" - hostname "$hostname" - sed -i -E "s/^(127\.0\.1\.1\s+).*/\1$hostname/" /etc/hosts + log "received DHCP hostname: $hostname" + mkdir -p /etc/hostname.d + echo "$hostname" > "/etc/hostname.d/90-dhcp-${interface}" + /usr/libexec/infix/hostname dhcp fi # drop info from this interface diff --git a/src/confd/src/ietf-system.c b/src/confd/src/ietf-system.c index 1a6b7eb5..eedd9427 100644 --- a/src/confd/src/ietf-system.c +++ b/src/confd/src/ietf-system.c @@ -206,11 +206,6 @@ done: return rc; } -static int sys_reload_services(void) -{ - return systemf("initctl -nbq touch sysklogd"); -} - #define TIMEZONE_CONF "/etc/timezone" #define TIMEZONE_PREV TIMEZONE_CONF "-" @@ -1558,11 +1553,10 @@ int hostnamefmt(struct confd *confd, const char *fmt, char *hostnm, size_t hostl static int change_hostname(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd) { - const char *hostip = "127.0.1.1"; char hostnm[65], domain[65]; - char buf[256], *fmt; - FILE *nfp, *fp; - int err, fd; + int rc = SR_ERR_OK; + char *fmt; + FILE *fp; if (event != SR_EV_DONE || !lydx_get_xpathf(diff, XPATH_HOSTNAME_)) return SR_ERR_OK; @@ -1572,82 +1566,36 @@ static int change_hostname(sr_session_ctx_t *session, struct lyd_node *config, s fmt = strdup(nm); if (hostnamefmt(confd, fmt, hostnm, sizeof(hostnm), domain, sizeof(domain))) { - err = SR_ERR_SYS; - goto err; + rc = SR_ERR_SYS; + goto failed; } - err = sethostname(hostnm, strlen(hostnm)); - if (err) { - ERROR("failed setting hostname"); - err = SR_ERR_SYS; - goto err; - } + /* Use hostname.d for deterministic hostname management */ + systemf("mkdir -p /etc/hostname.d"); + fp = fopen("/etc/hostname.d/50-configured", "w"); + if (!fp) + goto failed; + + fprintf(fp, "%s\n", hostnm); + fclose(fp); + + /* Handle domain name if present */ if (domain[0] && setdomainname(domain, strlen(domain))) { ERROR("failed setting domain name"); /* Not cause for failing this function */ } - fp = fopen(_PATH_HOSTNAME, "w"); - if (!fp) { - err = SR_ERR_INTERNAL; - goto err; + if (systemf("/usr/libexec/infix/hostname")) { + failed: + ERROR("failed setting hostname"); + rc = SR_ERR_SYS; } - fprintf(fp, "%s\n", hostnm); - fclose(fp); - - nfp = fopen(_PATH_HOSTS "+", "w"); - if (!nfp) { - err = SR_ERR_INTERNAL; - goto err; - } - fd = fileno(nfp); - if (fd == -1 || fchown(fd, 0, 0) || fchmod(fd, 0644)) { - fclose(nfp); - goto err; - } - - fp = fopen(_PATH_HOSTS, "r"); - if (!fp) { - err = SR_ERR_INTERNAL; - fclose(nfp); - goto err; - } - - while (fgets(buf, sizeof(buf), fp)) { - if (!strncmp(buf, hostip, strlen(hostip))) { - if (domain[0]) - snprintf(buf, sizeof(buf), "%s\t%s.%s %s\n", hostip, hostnm, domain, hostnm); - else - snprintf(buf, sizeof(buf), "%s\t%s\n", hostip, hostnm); - } - fputs(buf, nfp); - } - - fclose(fp); - fclose(nfp); - if (rename(_PATH_HOSTS "+", _PATH_HOSTS)) - ERRNO("Failed activating changes to "_PATH_HOSTS); - - /* skip in bootstrap, lldpd and avahi have not started yet */ - if (systemf("runlevel >/dev/null 2>&1")) - goto err; - - /* Inform any running lldpd and avahi of the change ... */ - systemf("initctl -bq status lldpd && lldpcli configure system hostname %s", hostnm); - systemf("initctl -bq status mdns && avahi-set-host-name %s", hostnm); - systemf("initctl -bq touch netbrowse"); -err: if (fmt) free(fmt); - - if (err) { - ERROR("Failed activating changes."); - return err; - } - if (sys_reload_services()) - return SR_ERR_SYS; + if (rc) + return rc; return SR_ERR_OK; } @@ -1656,6 +1604,7 @@ err: int ietf_system_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd) { int rc = SR_ERR_OK; + if ((rc = change_auth(session, config, diff, event, confd))) return rc; if ((rc = change_ntp(session, config, diff, event, confd)))