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-<iface>  - 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 <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2025-11-18 19:07:25 +01:00
parent 889120e9da
commit 90f619bfa6
4 changed files with 122 additions and 76 deletions
+74
View File
@@ -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-<iface> - 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
+18
View File
@@ -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
@@ -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
+22 -73
View File
@@ -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)))