Merge pull request #1442 from kernelkit/netd-backport

Backport fix to critical v26.02.0 regression
This commit is contained in:
Joachim Wiberg
2026-03-12 16:10:14 +01:00
committed by GitHub
11 changed files with 111 additions and 58 deletions
@@ -7,7 +7,7 @@ ACTION="$1"
IP_CACHE="/var/lib/misc/${interface}.cache" IP_CACHE="/var/lib/misc/${interface}.cache"
RESOLV_CONF="/run/resolvconf/interfaces/${interface}.conf" RESOLV_CONF="/run/resolvconf/interfaces/${interface}.conf"
NTPFILE="/run/chrony/dhcp-sources.d/${interface}.sources" 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}+" NEXT="${NAME}+"
[ -n "$broadcast" ] && BROADCAST="broadcast $broadcast" [ -n "$broadcast" ] && BROADCAST="broadcast $broadcast"
@@ -118,8 +118,6 @@ set_dhcp_routes()
# Reduce changes needed by comparing with previous route(s) # Reduce changes needed by comparing with previous route(s)
cmp -s "$NAME" "$NEXT" && return cmp -s "$NAME" "$NEXT" && return
mv "$NEXT" "$NAME" mv "$NEXT" "$NAME"
initctl reload netd
} }
clr_dhcp_routes() clr_dhcp_routes()
@@ -127,8 +125,6 @@ clr_dhcp_routes()
log "deleting DHCP routes" log "deleting DHCP routes"
[ -f "$NAME" ] || return [ -f "$NAME" ] || return
rm "$NAME" rm "$NAME"
initctl reload netd
} }
clr_dhcp_addresses() clr_dhcp_addresses()
+2 -1
View File
@@ -3,7 +3,7 @@ Change Log
All notable changes to the project are documented in this file. All notable changes to the project are documented in this file.
[v26.02.1][] - 2026-03-11 [v26.02.1][] - 2026-03-12
------------------------- -------------------------
### Changes ### Changes
@@ -17,6 +17,7 @@ All notable changes to the project are documented in this file.
- Fix #1389: legacy name limit in firewalld triggered problems with policy names - Fix #1389: legacy name limit in firewalld triggered problems with policy names
- Fix #1416: `show firewall` command show an error when the firewall is disabled - Fix #1416: `show firewall` command show an error when the firewall is disabled
- Fix #1438: default route from DHCP client not set at boot, regression in v26.02.0
- Fix instabilities in Zebra route manager after Frr upgrade in v26.02.0 - Fix instabilities in Zebra route manager after Frr upgrade in v26.02.0
- Fix regression in MVEBU SafeXcel Crypto Engine for Marvell Armada SOCs (37xx, - Fix regression in MVEBU SafeXcel Crypto Engine for Marvell Armada SOCs (37xx,
7k, 8k, and CN913x series). Firmware package lost in v26.01.0 7k, 8k, and CN913x series). Firmware package lost in v26.01.0
+2 -1
View File
@@ -2,9 +2,10 @@ config BR2_PACKAGE_NETD
bool "netd" bool "netd"
select BR2_PACKAGE_LIBITE select BR2_PACKAGE_LIBITE
select BR2_PACKAGE_LIBCONFUSE select BR2_PACKAGE_LIBCONFUSE
select BR2_PACKAGE_LIBEV
help help
Network route daemon. Manages static routes and RIP routing. 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. With FRR: Full routing via gRPC, vtysh, or frr.conf.
Without FRR: Standalone Linux backend via rtnetlink. Without FRR: Standalone Linux backend via rtnetlink.
+2 -2
View File
@@ -4,13 +4,13 @@
# #
################################################################################ ################################################################################
NETD_VERSION = 1.0 NETD_VERSION = 1.1.0
NETD_SITE_METHOD = local NETD_SITE_METHOD = local
NETD_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/netd NETD_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/netd
NETD_LICENSE = BSD-3-Clause NETD_LICENSE = BSD-3-Clause
NETD_LICENSE_FILES = LICENSE NETD_LICENSE_FILES = LICENSE
NETD_REDISTRIBUTE = NO NETD_REDISTRIBUTE = NO
NETD_DEPENDENCIES = libite libconfuse jansson NETD_DEPENDENCIES = libite libconfuse jansson libev
NETD_AUTORECONF = YES NETD_AUTORECONF = YES
NETD_CONF_ENV = CFLAGS="$(INFIX_CFLAGS)" NETD_CONF_ENV = CFLAGS="$(INFIX_CFLAGS)"
+1 -1
View File
@@ -8,7 +8,7 @@
#define XPATH_BASE_ "/ietf-routing:routing/control-plane-protocols/control-plane-protocol" #define XPATH_BASE_ "/ietf-routing:routing/control-plane-protocols/control-plane-protocol"
#define XPATH_OSPF_ XPATH_BASE_ "/ietf-ospf:ospf" #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_NEXT NETD_CONF "+"
#define NETD_CONF_PREV NETD_CONF "-" #define NETD_CONF_PREV NETD_CONF "-"
#define OSPFD_CONF "/etc/frr/ospfd.conf" #define OSPFD_CONF "/etc/frr/ospfd.conf"
+1 -1
View File
@@ -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_CPPFLAGS = -D_DEFAULT_SOURCE -D_GNU_SOURCE -I$(srcdir)/src
netd_CFLAGS = -W -Wall -Wextra netd_CFLAGS = -W -Wall -Wextra
netd_CFLAGS += $(libite_CFLAGS) $(libconfuse_CFLAGS) $(jansson_CFLAGS) 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 # Backend selection: FRR gRPC, FRR frr.conf, or Linux kernel
if HAVE_FRR_GRPC if HAVE_FRR_GRPC
+4 -4
View File
@@ -60,7 +60,7 @@ make install
netd uses [libconfuse](https://github.com/martinh/libconfuse) for configuration parsing, providing a clean and structured format. 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 ### Configuration Format
@@ -219,10 +219,10 @@ rip {
### Configuration Files ### 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 ```bash
/etc/netd/conf.d/ /etc/net.d/
├── 10-static.conf # Static routes ├── 10-static.conf # Static routes
├── 20-rip.conf # RIP configuration ├── 20-rip.conf # RIP configuration
└── 99-local.conf # Local overrides └── 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 │ SIGHUP
+11 -1
View File
@@ -1,5 +1,5 @@
AC_PREREQ(2.61) 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_INIT_AUTOMAKE(1.11 foreign subdir-objects)
AM_SILENT_RULES(yes) 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([libconfuse], [libconfuse >= 3.0])
PKG_CHECK_MODULES([jansson], [jansson >= 2.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$prefix" = xNONE && prefix=$ac_default_prefix
test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+86 -41
View File
@@ -2,6 +2,8 @@
#include <errno.h> #include <errno.h>
#include <getopt.h> #include <getopt.h>
#include <sys/inotify.h>
#include <ev.h>
#include <libite/lite.h> #include <libite/lite.h>
#include "netd.h" #include "netd.h"
@@ -9,9 +11,6 @@
int debug; 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 route_head active_routes = TAILQ_HEAD_INITIALIZER(active_routes);
static struct rip_config active_rip; static struct rip_config active_rip;
@@ -46,19 +45,7 @@ static int backend_apply(struct route_head *routes, struct rip_config *rip) {
} }
#endif #endif
static void sighup_handler(int sig) static ev_timer retry_w;
{
(void)sig;
INFO("Got SIGHUP, reloading ...");
do_reload = 1;
}
static void sigterm_handler(int sig)
{
(void)sig;
do_shutdown = 1;
}
static void route_list_free(struct route_head *head) 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 route_head new_routes = TAILQ_HEAD_INITIALIZER(new_routes);
struct rip_redistribute *redist; struct rip_redistribute *redist;
@@ -129,7 +116,7 @@ static void reload(void)
struct route *r; struct route *r;
int count = 0; int count = 0;
INFO("Reloading configuration"); DEBUG("Reloading configuration");
rip_config_init(&new_rip); rip_config_init(&new_rip);
@@ -148,11 +135,16 @@ static void reload(void)
/* Apply config via backend */ /* Apply config via backend */
if (backend_apply(&new_routes, &new_rip)) { 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); route_list_free(&new_routes);
rip_config_free(&new_rip); 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; return;
} }
ev_timer_stop(loop, &retry_w);
route_list_free(&active_routes); route_list_free(&active_routes);
TAILQ_INIT(&active_routes); TAILQ_INIT(&active_routes);
@@ -214,26 +206,59 @@ static void reload(void)
} }
} }
INFO("Configuration reloaded");
pidfile(NULL); 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) static int usage(int rc)
{ {
fprintf(stderr, fprintf(stderr,
"Usage: netd [-dh]\n" "Usage: netd [-dhv]\n"
" -d Enable debug (log to stderr)\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; return rc;
} }
int main(int argc, char *argv[]) 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; int log_opts = LOG_PID | LOG_NDELAY;
struct sigaction sa = { 0 }; int ifd = -1;
int c; int c;
while ((c = getopt(argc, argv, "dhp:")) != -1) { while ((c = getopt(argc, argv, "dhv")) != -1) {
switch (c) { switch (c) {
case 'd': case 'd':
log_opts |= LOG_PERROR; log_opts |= LOG_PERROR;
@@ -241,6 +266,9 @@ int main(int argc, char *argv[])
break; break;
case 'h': case 'h':
return usage(0); return usage(0);
case 'v':
puts("v" PACKAGE_VERSION);
return 0;
default: default:
return usage(1); return usage(1);
} }
@@ -248,15 +276,7 @@ int main(int argc, char *argv[])
openlog("netd", log_opts, LOG_DAEMON); openlog("netd", log_opts, LOG_DAEMON);
setlogmask(LOG_UPTO(LOG_INFO)); setlogmask(LOG_UPTO(LOG_INFO));
INFO("starting"); INFO("v%s starting", PACKAGE_VERSION);
/* 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);
if (backend_init()) { if (backend_init()) {
ERROR("Failed to initialize backend"); ERROR("Failed to initialize backend");
@@ -267,19 +287,44 @@ int main(int argc, char *argv[])
TAILQ_INIT(&active_routes); TAILQ_INIT(&active_routes);
rip_config_init(&active_rip); rip_config_init(&active_rip);
/* Initial load */ /* Signal watchers */
do_reload = 1; ev_signal_init(&sighup_w, sighup_cb, SIGHUP);
ev_signal_start(loop, &sighup_w);
while (!do_shutdown) { ev_signal_init(&sigterm_w, sigterm_cb, SIGTERM);
if (do_reload) { ev_signal_start(loop, &sigterm_w);
do_reload = 0;
reload(); ev_signal_init(&sigint_w, sigterm_cb, SIGINT);
} ev_signal_start(loop, &sigint_w);
pause();
/* 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"); INFO("shutting down");
if (ifd >= 0)
close(ifd);
route_list_free(&active_routes); route_list_free(&active_routes);
rip_config_free(&active_rip); rip_config_free(&active_rip);
backend_cleanup(); backend_cleanup();
+1 -1
View File
@@ -21,7 +21,7 @@ extern int debug;
#define INFO(fmt, args...) LOG(LOG_INFO, fmt, ##args) #define INFO(fmt, args...) LOG(LOG_INFO, fmt, ##args)
#define DEBUG(fmt, args...) do { if (debug) LOG(LOG_DEBUG, fmt, ##args); } while (0) #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 */ /* Nexthop types */
enum nh_type { enum nh_type {