From 221fea13a20a12ce4dc7a3c7d9a8e02e74bc05b2 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 12 Mar 2026 10:13:36 +0100 Subject: [PATCH 1/4] netd: watch conf.d with inotify, retry on backend failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two races could prevent DHCP-learned default routes from being installed at boot: 1. The signal from the DHCP client script could be lost leaving conf.d updated but frr.conf stale. 2. Even when the signal was received, 'vtysh -b' could fail because the FRR daemon chain (mgmtd→zebra→staticd) writes PID files before being fully operational, causing netd to give up with no retry. Fix both by refactoring netd to use libev: - Use an inotify watch of CONF_DIR, so netd reacts directly to file changes without depending on signal delivery. - On backend_apply() failure, schedule a retry in 5s and call pidfile() unconditionally so dependent services are not blocked waiting for the Finit condition 'pid/netd' to be satisfied. We also take this opportunity to rename /etc/netd/conf.d/ to /etc/net.d/ The extra /etc/netd/ directory level served no purpose — nothing else lives there. Flatten to /etc/net.d/ which reads more naturally as the drop-in directory for network configuration snippets. Also, reduce logging a bit since each netd backend already logs success or fail which is sufficient to know that a configuration change has been applied or not. Finally, with the new inotify processing in netd it's redundant to call 'initctl reload netd' from the udhcpc script, in fact it will only cause unnecessary overhead, so we drop it. Fixes #1438 Signed-off-by: Joachim Wiberg --- board/common/rootfs/etc/netd/conf.d/.empty | 0 .../rootfs/usr/share/udhcpc/default.script | 6 +- package/netd/Config.in | 3 +- package/netd/netd.mk | 4 +- src/confd/src/routing.c | 2 +- src/netd/Makefile.am | 2 +- src/netd/README.md | 8 +- src/netd/configure.ac | 12 +- src/netd/src/netd.c | 127 ++++++++++++------ src/netd/src/netd.h | 2 +- 10 files changed, 109 insertions(+), 57 deletions(-) delete mode 100644 board/common/rootfs/etc/netd/conf.d/.empty diff --git a/board/common/rootfs/etc/netd/conf.d/.empty b/board/common/rootfs/etc/netd/conf.d/.empty deleted file mode 100644 index e69de29b..00000000 diff --git a/board/common/rootfs/usr/share/udhcpc/default.script b/board/common/rootfs/usr/share/udhcpc/default.script index c2a8c24a..a28eb28f 100755 --- a/board/common/rootfs/usr/share/udhcpc/default.script +++ b/board/common/rootfs/usr/share/udhcpc/default.script @@ -7,7 +7,7 @@ ACTION="$1" IP_CACHE="/var/lib/misc/${interface}.cache" RESOLV_CONF="/run/resolvconf/interfaces/${interface}.conf" NTPFILE="/run/chrony/dhcp-sources.d/${interface}.sources" -NAME="/etc/netd/conf.d/${interface}-dhcp.conf" +NAME="/etc/net.d/${interface}-dhcp.conf" NEXT="${NAME}+" [ -n "$broadcast" ] && BROADCAST="broadcast $broadcast" @@ -118,8 +118,6 @@ set_dhcp_routes() # Reduce changes needed by comparing with previous route(s) cmp -s "$NAME" "$NEXT" && return mv "$NEXT" "$NAME" - - initctl reload netd } clr_dhcp_routes() @@ -127,8 +125,6 @@ clr_dhcp_routes() log "deleting DHCP routes" [ -f "$NAME" ] || return rm "$NAME" - - initctl reload netd } clr_dhcp_addresses() diff --git a/package/netd/Config.in b/package/netd/Config.in index 94bd4a92..96be1fc3 100644 --- a/package/netd/Config.in +++ b/package/netd/Config.in @@ -2,9 +2,10 @@ config BR2_PACKAGE_NETD bool "netd" select BR2_PACKAGE_LIBITE select BR2_PACKAGE_LIBCONFUSE + select BR2_PACKAGE_LIBEV help Network route daemon. Manages static routes and RIP routing. - Reads configuration from /etc/netd/conf.d/*.conf. + Reads configuration from /etc/net.d/*.conf. With FRR: Full routing via gRPC, vtysh, or frr.conf. Without FRR: Standalone Linux backend via rtnetlink. diff --git a/package/netd/netd.mk b/package/netd/netd.mk index 60ab4535..8a658af0 100644 --- a/package/netd/netd.mk +++ b/package/netd/netd.mk @@ -4,13 +4,13 @@ # ################################################################################ -NETD_VERSION = 1.0 +NETD_VERSION = 1.1.0 NETD_SITE_METHOD = local NETD_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/netd NETD_LICENSE = BSD-3-Clause NETD_LICENSE_FILES = LICENSE NETD_REDISTRIBUTE = NO -NETD_DEPENDENCIES = libite libconfuse jansson +NETD_DEPENDENCIES = libite libconfuse jansson libev NETD_AUTORECONF = YES NETD_CONF_ENV = CFLAGS="$(INFIX_CFLAGS)" diff --git a/src/confd/src/routing.c b/src/confd/src/routing.c index 5bb8b45a..1ef2a5f9 100644 --- a/src/confd/src/routing.c +++ b/src/confd/src/routing.c @@ -8,7 +8,7 @@ #define XPATH_BASE_ "/ietf-routing:routing/control-plane-protocols/control-plane-protocol" #define XPATH_OSPF_ XPATH_BASE_ "/ietf-ospf:ospf" -#define NETD_CONF "/etc/netd/conf.d/confd.conf" +#define NETD_CONF "/etc/net.d/confd.conf" #define NETD_CONF_NEXT NETD_CONF "+" #define NETD_CONF_PREV NETD_CONF "-" #define OSPFD_CONF "/etc/frr/ospfd.conf" diff --git a/src/netd/Makefile.am b/src/netd/Makefile.am index 16803eac..0de3ad9e 100644 --- a/src/netd/Makefile.am +++ b/src/netd/Makefile.am @@ -6,7 +6,7 @@ netd_SOURCES = src/netd.c src/netd.h src/config.c src/config.h netd_CPPFLAGS = -D_DEFAULT_SOURCE -D_GNU_SOURCE -I$(srcdir)/src netd_CFLAGS = -W -Wall -Wextra netd_CFLAGS += $(libite_CFLAGS) $(libconfuse_CFLAGS) $(jansson_CFLAGS) -netd_LDADD = $(libite_LIBS) $(libconfuse_LIBS) $(jansson_LIBS) +netd_LDADD = $(libite_LIBS) $(libconfuse_LIBS) $(jansson_LIBS) $(EV_LIBS) # Backend selection: FRR gRPC, FRR frr.conf, or Linux kernel if HAVE_FRR_GRPC diff --git a/src/netd/README.md b/src/netd/README.md index 5ebcfc80..aa0aca35 100644 --- a/src/netd/README.md +++ b/src/netd/README.md @@ -60,7 +60,7 @@ make install netd uses [libconfuse](https://github.com/martinh/libconfuse) for configuration parsing, providing a clean and structured format. -Configuration files are placed in `/etc/netd/conf.d/` with the `.conf` extension. Files are processed in alphabetical order. +Configuration files are placed in `/etc/net.d/` with the `.conf` extension. Files are processed in alphabetical order. ### Configuration Format @@ -219,10 +219,10 @@ rip { ### Configuration Files -Configuration files must be placed in `/etc/netd/conf.d/` with the `.conf` extension: +Configuration files must be placed in `/etc/net.d/` with the `.conf` extension: ```bash -/etc/netd/conf.d/ +/etc/net.d/ ├── 10-static.conf # Static routes ├── 20-rip.conf # RIP configuration └── 99-local.conf # Local overrides @@ -256,7 +256,7 @@ netd validates configuration on reload. Check syslog for errors. ``` ┌─────────┐ -│ confd │ Writes /etc/netd/conf.d/confd.conf +│ confd │ Writes /etc/net.d/confd.conf └────┬────┘ │ SIGHUP ▼ diff --git a/src/netd/configure.ac b/src/netd/configure.ac index ca2fd2b1..04b7fa7a 100644 --- a/src/netd/configure.ac +++ b/src/netd/configure.ac @@ -1,5 +1,5 @@ AC_PREREQ(2.61) -AC_INIT([netd], [1.0.0], [https://github.com/kernelkit/infix/issues]) +AC_INIT([netd], [1.1.0], [https://github.com/kernelkit/infix/issues]) AM_INIT_AUTOMAKE(1.11 foreign subdir-objects) AM_SILENT_RULES(yes) @@ -81,6 +81,16 @@ PKG_CHECK_MODULES([libite], [libite >= 2.6.1]) PKG_CHECK_MODULES([libconfuse], [libconfuse >= 3.0]) PKG_CHECK_MODULES([jansson], [jansson >= 2.0]) +AC_CHECK_HEADER([ev.h], + [saved_LIBS="$LIBS" + AC_CHECK_LIB([ev], [ev_loop_new], + [EV_LIBS="-lev"], + [AC_MSG_ERROR("libev not found")]) + LIBS="$saved_LIBS"], + [AC_MSG_ERROR("ev.h not found")] +) +AC_SUBST([EV_LIBS]) + test "x$prefix" = xNONE && prefix=$ac_default_prefix test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' diff --git a/src/netd/src/netd.c b/src/netd/src/netd.c index 3d120f1f..c8f183c1 100644 --- a/src/netd/src/netd.c +++ b/src/netd/src/netd.c @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include "netd.h" @@ -9,9 +11,6 @@ int debug; -static sig_atomic_t do_reload; -static sig_atomic_t do_shutdown; - static struct route_head active_routes = TAILQ_HEAD_INITIALIZER(active_routes); static struct rip_config active_rip; @@ -46,19 +45,7 @@ static int backend_apply(struct route_head *routes, struct rip_config *rip) { } #endif -static void sighup_handler(int sig) -{ - (void)sig; - - INFO("Got SIGHUP, reloading ..."); - do_reload = 1; -} - -static void sigterm_handler(int sig) -{ - (void)sig; - do_shutdown = 1; -} +static ev_timer retry_w; static void route_list_free(struct route_head *head) { @@ -118,7 +105,7 @@ static void rip_config_free(struct rip_config *cfg) } } -static void reload(void) +static void reload(struct ev_loop *loop) { struct route_head new_routes = TAILQ_HEAD_INITIALIZER(new_routes); struct rip_redistribute *redist; @@ -129,7 +116,7 @@ static void reload(void) struct route *r; int count = 0; - INFO("Reloading configuration"); + DEBUG("Reloading configuration"); rip_config_init(&new_rip); @@ -148,11 +135,16 @@ static void reload(void) /* Apply config via backend */ if (backend_apply(&new_routes, &new_rip)) { - ERROR("Failed applying config via backend"); + ERROR("Failed applying config via backend, retry in 5s"); route_list_free(&new_routes); rip_config_free(&new_rip); + ev_timer_stop(loop, &retry_w); + ev_timer_set(&retry_w, 5., 0.); + ev_timer_start(loop, &retry_w); + pidfile(NULL); return; } + ev_timer_stop(loop, &retry_w); route_list_free(&active_routes); TAILQ_INIT(&active_routes); @@ -214,26 +206,59 @@ static void reload(void) } } - INFO("Configuration reloaded"); pidfile(NULL); } +static void inotify_cb(struct ev_loop *loop, ev_io *w, int revents) +{ + char buf[sizeof(struct inotify_event) + NAME_MAX + 1]; + + (void)revents; + while (read(w->fd, buf, sizeof(buf)) > 0) + ; + DEBUG("conf.d changed, triggering reload"); + reload(loop); +} + +static void sighup_cb(struct ev_loop *loop, ev_signal *w, int revents) +{ + (void)w; (void)revents; + INFO("Got SIGHUP, reloading ..."); + reload(loop); +} + +static void sigterm_cb(struct ev_loop *loop, ev_signal *w, int revents) +{ + (void)w; (void)revents; + ev_break(loop, EVBREAK_ALL); +} + +static void retry_cb(struct ev_loop *loop, ev_timer *w, int revents) +{ + (void)w; (void)revents; + reload(loop); +} + static int usage(int rc) { fprintf(stderr, - "Usage: netd [-dh]\n" + "Usage: netd [-dhv]\n" " -d Enable debug (log to stderr)\n" - " -h Show this help text\n"); + " -h Show this help text\n" + " -v Show version and exit\n"); return rc; } int main(int argc, char *argv[]) { + struct ev_loop *loop = EV_DEFAULT; + ev_signal sighup_w, sigterm_w, sigint_w; + ev_io inotify_w; int log_opts = LOG_PID | LOG_NDELAY; - struct sigaction sa = { 0 }; + int ifd = -1; int c; - while ((c = getopt(argc, argv, "dhp:")) != -1) { + while ((c = getopt(argc, argv, "dhv")) != -1) { switch (c) { case 'd': log_opts |= LOG_PERROR; @@ -241,6 +266,9 @@ int main(int argc, char *argv[]) break; case 'h': return usage(0); + case 'v': + puts("v" PACKAGE_VERSION); + return 0; default: return usage(1); } @@ -248,15 +276,7 @@ int main(int argc, char *argv[]) openlog("netd", log_opts, LOG_DAEMON); setlogmask(LOG_UPTO(LOG_INFO)); - INFO("starting"); - - /* Set up signal handlers */ - sa.sa_handler = sighup_handler; - sigaction(SIGHUP, &sa, NULL); - - sa.sa_handler = sigterm_handler; - sigaction(SIGTERM, &sa, NULL); - sigaction(SIGINT, &sa, NULL); + INFO("v%s starting", PACKAGE_VERSION); if (backend_init()) { ERROR("Failed to initialize backend"); @@ -267,19 +287,44 @@ int main(int argc, char *argv[]) TAILQ_INIT(&active_routes); rip_config_init(&active_rip); - /* Initial load */ - do_reload = 1; + /* Signal watchers */ + ev_signal_init(&sighup_w, sighup_cb, SIGHUP); + ev_signal_start(loop, &sighup_w); - while (!do_shutdown) { - if (do_reload) { - do_reload = 0; - reload(); - } - pause(); + ev_signal_init(&sigterm_w, sigterm_cb, SIGTERM); + ev_signal_start(loop, &sigterm_w); + + ev_signal_init(&sigint_w, sigterm_cb, SIGINT); + ev_signal_start(loop, &sigint_w); + + /* Retry timer — one-shot, started only on backend failure */ + ev_timer_init(&retry_w, retry_cb, 0., 0.); + + /* Watch conf.d for changes so we don't rely solely on signals */ + mkdir(CONF_DIR, 0755); + ifd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK); + if (ifd < 0) { + ERROR("inotify_init1: %s, falling back to signals only", strerror(errno)); + } else if (inotify_add_watch(ifd, CONF_DIR, + IN_CLOSE_WRITE | IN_DELETE | + IN_MOVED_TO | IN_MOVED_FROM) < 0) { + ERROR("inotify_add_watch %s: %s", CONF_DIR, strerror(errno)); + close(ifd); + ifd = -1; + } else { + ev_io_init(&inotify_w, inotify_cb, ifd, EV_READ); + ev_io_start(loop, &inotify_w); } + /* Initial load */ + reload(loop); + + ev_run(loop, 0); + INFO("shutting down"); + if (ifd >= 0) + close(ifd); route_list_free(&active_routes); rip_config_free(&active_rip); backend_cleanup(); diff --git a/src/netd/src/netd.h b/src/netd/src/netd.h index 99fb4ab2..80df72b4 100644 --- a/src/netd/src/netd.h +++ b/src/netd/src/netd.h @@ -21,7 +21,7 @@ extern int debug; #define INFO(fmt, args...) LOG(LOG_INFO, fmt, ##args) #define DEBUG(fmt, args...) do { if (debug) LOG(LOG_DEBUG, fmt, ##args); } while (0) -#define CONF_DIR "/etc/netd/conf.d" +#define CONF_DIR "/etc/net.d" /* Nexthop types */ enum nh_type { From ba8d1f6b4a0205ce58051942e09f0ba138c6a741 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 12 Mar 2026 11:34:12 +0100 Subject: [PATCH 2/4] confd: fix DHCP client hostname reporting, rpi-%m -> rpi-c0-ff-ee Ensure we translate the hostname before we generate the udhcpc command line options, otherwise we'll send '-x hostname:rpi-%m' to udhcpc. Also update all board specific factory configs with the missing setting '"value": "auto"' for the DHCP hostname option. This to match what we already do when we infer options when enabling a DHCP client in the CLI. Signed-off-by: Joachim Wiberg --- .../bananapi,bpi-r3/etc/factory-config.cfg | 31 ++++++++++--------- .../etc/factory-config.cfg | 31 ++++++++++--------- .../bananapi,bpi-r64/etc/factory-config.cfg | 15 ++++----- .../etc/factory-config.cfg | 3 +- .../etc/factory-config.cfg | 3 +- .../raspberrypi,400/etc/factory-config.cfg | 3 +- .../etc/factory-config.cfg | 3 +- src/confd/src/dhcp-common.c | 20 ++++++------ 8 files changed, 59 insertions(+), 50 deletions(-) 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 723b25a2..859333aa 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 @@ -21,21 +21,21 @@ { "name": "radio0", "class": "infix-hardware:wifi", - "infix-hardware:wifi-radio": { - "country-code": "DE", - "band": "2.4GHz", - "channel": "auto" - } - }, - { - "name": "radio1", - "class": "infix-hardware:wifi", - "infix-hardware:wifi-radio": { - "country-code": "DE", + "infix-hardware:wifi-radio": { + "country-code": "DE", + "band": "2.4GHz", + "channel": "auto" + } + }, + { + "name": "radio1", + "class": "infix-hardware:wifi", + "infix-hardware:wifi-radio": { + "country-code": "DE", "band": "5GHz", "channel": "auto" + } } - } ] }, "ietf-interfaces:interfaces": { @@ -130,7 +130,8 @@ "id": "domain" }, { - "id": "hostname" + "id": "hostname", + "value": "auto" }, { "id": "dns-server" @@ -179,7 +180,7 @@ } } }, - "infix-interfaces:bridge-port": { + "infix-interfaces:bridge-port": { "bridge": "br0" } }, @@ -195,7 +196,7 @@ } } }, - "infix-interfaces:bridge-port": { + "infix-interfaces:bridge-port": { "bridge": "br0" } } diff --git a/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3mini/etc/factory-config.cfg b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3mini/etc/factory-config.cfg index 19068393..269e927c 100644 --- a/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3mini/etc/factory-config.cfg +++ b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3mini/etc/factory-config.cfg @@ -21,21 +21,21 @@ { "name": "radio0", "class": "infix-hardware:wifi", - "infix-hardware:wifi-radio": { - "country-code": "DE", - "band": "2.4GHz", - "channel": "auto" - } - }, - { - "name": "radio1", - "class": "infix-hardware:wifi", - "infix-hardware:wifi-radio": { - "country-code": "DE", + "infix-hardware:wifi-radio": { + "country-code": "DE", + "band": "2.4GHz", + "channel": "auto" + } + }, + { + "name": "radio1", + "class": "infix-hardware:wifi", + "infix-hardware:wifi-radio": { + "country-code": "DE", "band": "5GHz", "channel": "auto" + } } - } ] }, "ietf-interfaces:interfaces": { @@ -96,7 +96,8 @@ "id": "domain" }, { - "id": "hostname" + "id": "hostname", + "value": "auto" }, { "id": "dns-server" @@ -145,7 +146,7 @@ } } }, - "infix-interfaces:bridge-port": { + "infix-interfaces:bridge-port": { "bridge": "br0" } }, @@ -161,7 +162,7 @@ } } }, - "infix-interfaces:bridge-port": { + "infix-interfaces:bridge-port": { "bridge": "br0" } } diff --git a/board/aarch64/bananapi-bpi-r64/rootfs/usr/share/product/bananapi,bpi-r64/etc/factory-config.cfg b/board/aarch64/bananapi-bpi-r64/rootfs/usr/share/product/bananapi,bpi-r64/etc/factory-config.cfg index bb9eb50c..6f4c1939 100644 --- a/board/aarch64/bananapi-bpi-r64/rootfs/usr/share/product/bananapi,bpi-r64/etc/factory-config.cfg +++ b/board/aarch64/bananapi-bpi-r64/rootfs/usr/share/product/bananapi,bpi-r64/etc/factory-config.cfg @@ -14,11 +14,11 @@ { "name": "radio0", "class": "infix-hardware:wifi", - "infix-hardware:wifi-radio": { - "country-code": "DE", - "band": "2.4GHz", - "channel": "auto" - } + "infix-hardware:wifi-radio": { + "country-code": "DE", + "band": "2.4GHz", + "channel": "auto" + } } ] }, @@ -104,7 +104,8 @@ "id": "domain" }, { - "id": "hostname" + "id": "hostname", + "value": "auto" }, { "id": "dns-server" @@ -153,7 +154,7 @@ } } }, - "infix-interfaces:bridge-port": { + "infix-interfaces:bridge-port": { "bridge": "br0" } } 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 b99a3410..1a435d34 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 @@ -71,7 +71,8 @@ "id": "domain" }, { - "id": "hostname" + "id": "hostname", + "value": "auto" }, { "id": "dns-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 bcacd51a..365ae111 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 @@ -62,7 +62,8 @@ "id": "domain" }, { - "id": "hostname" + "id": "hostname", + "value": "auto" }, { "id": "dns-server" diff --git a/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,400/etc/factory-config.cfg b/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,400/etc/factory-config.cfg index 94c61bf2..886486f3 100644 --- a/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,400/etc/factory-config.cfg +++ b/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,400/etc/factory-config.cfg @@ -76,7 +76,8 @@ "id": "domain" }, { - "id": "hostname" + "id": "hostname", + "value": "auto" }, { "id": "dns-server" diff --git a/board/arm/raspberrypi-rpi2/rootfs/usr/share/product/raspberrypi,2-model-b/etc/factory-config.cfg b/board/arm/raspberrypi-rpi2/rootfs/usr/share/product/raspberrypi,2-model-b/etc/factory-config.cfg index a38b08c4..e13897ac 100644 --- a/board/arm/raspberrypi-rpi2/rootfs/usr/share/product/raspberrypi,2-model-b/etc/factory-config.cfg +++ b/board/arm/raspberrypi-rpi2/rootfs/usr/share/product/raspberrypi,2-model-b/etc/factory-config.cfg @@ -54,7 +54,8 @@ "id": "domain" }, { - "id": "hostname" + "id": "hostname", + "value": "auto" }, { "id": "dns-server" diff --git a/src/confd/src/dhcp-common.c b/src/confd/src/dhcp-common.c index 282af1dd..d13586f6 100644 --- a/src/confd/src/dhcp-common.c +++ b/src/confd/src/dhcp-common.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: BSD-3-Clause */ #include +#include #include #include #include @@ -70,21 +71,20 @@ int dhcp_option_lookup(const struct lyd_node *id) char *dhcp_hostname(struct lyd_node *cfg, char *str, size_t len) { + char hostname[HOST_NAME_MAX + 1] = { 0 }; struct lyd_node *node; - const char *hostname; - char *ptr; + const char *fmt; node = lydx_get_xpathf(cfg, "/ietf-system:system/hostname"); if (!node) return NULL; - hostname = lyd_get_value(node); - if (!hostname || hostname[0] == 0) + fmt = lyd_get_value(node); + if (!fmt || !fmt[0]) return NULL; - ptr = strchr(hostname, '.'); - if (ptr) - *ptr = 0; + if (hostnamefmt(&confd, fmt, hostname, sizeof(hostname), NULL, 0) || !hostname[0]) + return NULL; snprintf(str, len, "-x hostname:%s ", hostname); @@ -262,10 +262,12 @@ int ifchange_cand_infer_dhcp(sr_session_ctx_t *session, const char *xpath) /* Check if options already exist */ err = srx_nitems(session, &cnt, "%s/option", path); - if (err || cnt) { - ERROR("%s(): no %s/options err %d cnt %zu", __func__, path, err, cnt); + if (err) { + ERROR("%s(): failed querying %s/option: %d", __func__, path, err); goto out; } + if (cnt) + goto out; /* Infer options based on IPv4 or IPv6 */ if (strstr(path, ":ipv4/")) From 43f3bf0322951321a5f512d51bf71ef848c06de3 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 12 Mar 2026 11:49:05 +0100 Subject: [PATCH 3/4] board/common: make dnsmasq authoritative, drop redundant options Set dhcp-authoritative so clients with unexpired leases are not NAKed after a router crash or factory reset that clears the lease database. Drop the untagged suppress directives for options 3 (router) and 6 (dns-server): they conflict with the global options confd writes to dnsmasq.d/, causing spurious "Ignoring duplicate dhcp-option" warnings. Per-subnet tagged options already suppress auto-detection, and without an active dhcp-range there is nothing to suppress anyway. Signed-off-by: Joachim Wiberg --- board/common/rootfs/etc/dnsmasq.conf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/board/common/rootfs/etc/dnsmasq.conf b/board/common/rootfs/etc/dnsmasq.conf index 557cc1ac..1850d802 100644 --- a/board/common/rootfs/etc/dnsmasq.conf +++ b/board/common/rootfs/etc/dnsmasq.conf @@ -2,6 +2,7 @@ # managed by openresolv. DHCP lease, VPN tunnel establishment, # and similar events feed servers and configuration to dnsmasq. domain-needed +dhcp-authoritative # Allow configuration and cache clear over D-Bus enable-dbus @@ -10,8 +11,6 @@ enable-dbus #dhcp-option=option:netmask #dhcp-option=28 # option:broadcast #dhcp-option=option:domain-name -dhcp-option=option:router -dhcp-option=option:dns-server dhcp-option=12 # option:hostname # Generated by openresolv From 49c66ee957b8ca8a466b578b6f24e246c17d2b90 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 12 Mar 2026 12:07:19 +0100 Subject: [PATCH 4/4] doc: update ChangeLog Signed-off-by: Joachim Wiberg --- doc/ChangeLog.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 2de24082..4a359329 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -32,6 +32,18 @@ All notable changes to the project are documented in this file. ### Fixes +- Fix #1438: default route from DHCP client not set at boot, regression + introduced in v26.02.0 +- Fix DHCP client not sending hostname to server (e.g. for `dnsmasq` lease + registration). All board factory configs had the hostname option without + `value: auto`, so `udhcpc` only requested the option instead of sending it. + Setting up a DHCP client manually was not affected. Also, the hostname + format string (e.g. `rpi-%m`) was not expanded to the actual system hostname + before being passed to `udhcpc` +- Fix dnsmasq logging spurious "Ignoring duplicate dhcp-option" warnings for + options 3 (router) and 6 (dns-server) on every DHCP transaction. Add + `dhcp-authoritative` so clients with unexpired leases are not NAKed after + a router crash or factory reset that clears the lease database - Fix #1387: `infix.local` now resolves to exactly one device per LAN. Previously all Infix devices claimed both `hostname.local` and `infix.local`, causing avahi to append `#2`, `#3` suffixes to the shared alias on busy networks. Assignment