mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
netd: watch conf.d with inotify, retry on backend failure
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 <troglobit@gmail.com>
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
+4
-4
@@ -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
|
||||
▼
|
||||
|
||||
+11
-1
@@ -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}'
|
||||
|
||||
|
||||
+86
-41
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <ev.h>
|
||||
#include <libite/lite.h>
|
||||
|
||||
#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();
|
||||
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user