linkd

Control plane daemon for unos
git clone git://git.finwo.net/app/linkd
Log | Files | Refs | README

commit ff736bc10baadea6c66cc4761ecb7de54571b4b8
parent dcecd3c0e451813776a15505a9d71ce99b449081
Author: finwo <finwo@pm.me>
Date:   Wed, 30 Sep 2026 15:16:08 +0200

Wake the poll loop on SIGCHLD so a dead dhcp client is reported at once

Diffstat:
Msrc/dhcp/client.c | 23+++++++++++++++++++++++
Msrc/netlink/netlink.c | 3++-
Mtests/unit/test_dhcp.sh | 11++++++++++-
3 files changed, 35 insertions(+), 2 deletions(-)

diff --git a/src/dhcp/client.c b/src/dhcp/client.c @@ -73,6 +73,28 @@ static char *find_in_path(const char *bin) { } // --------------------------------------------------------------------------- + +// poll(2) is never restarted, so this drops nl_run out of its wait and the +// reap at the top of that loop runs immediately. SA_RESTART keeps the +// blocking waitpid in plugin.c from being interrupted. +static void sigchld_noop(int sig) { (void)sig; } + +static void arm_sigchld(void) { + static int armed = 0; + if (armed) return; + armed = 1; + + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sigchld_noop; + sa.sa_flags = SA_RESTART; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGCHLD, &sa, NULL) != 0) { + log_warn("dhcp: sigaction SIGCHLD: %s", strerror(errno)); + } +} + +// --------------------------------------------------------------------------- // process table static struct dhcp_proc *proc_find(const char *ifname) { @@ -107,6 +129,7 @@ int dhcp_start(const char *ifname) { return -1; } const char *const *tpl = (const char *const *)cfg->dhcp_client; + arm_sigchld(); char **argv = calloc(argc + 1, sizeof(char *)); if (!argv) { diff --git a/src/netlink/netlink.c b/src/netlink/netlink.c @@ -587,6 +587,8 @@ int nl_run(int fd, int resync_interval_s) { clock_gettime(CLOCK_MONOTONIC, &last_resync); while (!nl_stop) { + dhcp_reap(); + // Netlink occupies slot 0; the IPC server contributes its listeners and // every live connection after it. struct pollfd pfds[1 + IPC_MAX_POLLFDS]; @@ -622,7 +624,6 @@ int nl_run(int fd, int resync_interval_s) { log_error("netlink: poll failed: %s", strerror(errno)); return -1; } - dhcp_reap(); if (!pr) continue; if (nfds > 1) ipc_dispatch(&pfds[1], nfds - 1); diff --git a/tests/unit/test_dhcp.sh b/tests/unit/test_dhcp.sh @@ -30,8 +30,17 @@ iface swp-static inet static address 10.9.9.9/24 EOF +# An empty netns, because reaping is driven by SIGCHLD waking poll(2): on a +# host with ambient netlink traffic the loop spins anyway and a broken reap +# still passes. 3s for the same reason -- the wake is causal, not timed. +if unshare --user --map-root-user --net true 2>/dev/null; then + in_ns() { unshare --user --map-root-user --net "$@"; } +else + in_ns() { "$@"; } +fi + run_linkd() { - timeout 15 env PATH="${T}/emptybin" DHCP_LOG="${T}/dhcp.log" \ + in_ns timeout 3 env PATH="${T}/emptybin" DHCP_LOG="${T}/dhcp.log" \ "${BIN}" --config "$1" --resync-interval 0 \ >"${T}/linkd.log" 2>&1 || true cat "${T}/linkd.log"