From 067d14a96f837a9521dd4918427806d480d94c5c Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Thu, 16 Apr 2026 13:01:59 +0100 Subject: [PATCH 01/19] dhcpsd: -w waits for dynmically created interfaces The interface you may want to configure might not exist when dhcpsd first starts. The -w option will cause dhcpcd to only configure an interface when it recieves a message on it. We assume if we have received a message it's ready as we cannot guess which address to use. So ensure the server address is added before the interface is marked as up! If the interface is marked as down then the interface is deconfigured. This allows dhcpsd to be used in-place of the macOS DHCP server as provided by the UTM framework. --- configure | 32 +++- src/Makefile | 9 +- src/bpf.c | 9 +- src/common.c | 15 +- src/common.h | 3 +- src/dhcp.c | 112 +++++------ src/dhcp.h | 7 +- src/dhcpsd.8.in | 12 +- src/dhcpsd.c | 156 ++++++++++------ src/dhcpsd.h | 26 ++- src/if.c | 304 +++++++++++++++++++++--------- src/if.h | 5 + src/if_ether.c | 1 + src/plugins/auto.c | 12 +- src/priv.c | 258 ++++++++++++++++++++++++++ src/priv.h | 41 +++++ src/route.c | 185 +++++++++++++++++++ src/service.c | 12 +- src/unpriv.c | 449 +++++++++++++++++++++++++++++++++++++++++++-- src/unpriv.h | 7 + 20 files changed, 1399 insertions(+), 256 deletions(-) create mode 100644 src/priv.c create mode 100644 src/priv.h create mode 100644 src/route.c diff --git a/configure b/configure index a1a3216..e4868b2 100755 --- a/configure +++ b/configure @@ -321,9 +321,9 @@ EOF echo "yes" echo "SANITIZEADDRESS= yes" >>$CONFIG_MK echo "CPPFLAGS+= -DASAN" >>$CONFIG_MK - echo "CFLAGS+= -fsanitize=address" >>$CONFIG_MK + echo "CFLAGS+= -fsanitize=address,undefined" >>$CONFIG_MK echo "CFLAGS+= -fno-omit-frame-pointer" >>$CONFIG_MK - echo "LDFLAGS+= -fsanitize=address" >>$CONFIG_MK + echo "LDFLAGS+= -fsanitize=address,undefined" >>$CONFIG_MK else echo "no" fi @@ -591,6 +591,30 @@ fi rm -rf _inet_ntoa.* _inet_ntoa $abort && exit 1 +if [ -z "$LINK_SRC" ]; then + printf "Testing for route ..." + cat << EOF >_route.c +#include +int main(void) { +#ifdef RTM_IFINFO + return 0; +#endif +} +EOF + if $XCC _route.c -o _route 2>&3; then + LINK_SRC="route.c" + echo "yes" + else + echo "no" + fi + rm -rf _route.* _route +fi +if [ -z "$LINK_SRC" ]; then + echo "no mechanism for detecting interface departures found" >&2 + exit 1 +fi +echo "LINK_SRC= $LINK_SRC" >>$CONFIG_MK + if [ -z "$SETPROCTITLE" ]; then printf "Testing for setproctitle ... " cat << EOF >_setproctitle.c @@ -742,7 +766,5 @@ echo " LIBDIR = $LIBDIR" echo " DATADIR = $DATADIR" echo " RUNDIR = $RUNDIR" echo " MANDIR = $MANDIR" -if [ "$PRIVSEP" = yes ]; then - echo " PRIVSEPUSER = $PRIVSEP_USER" -fi +echo " DHCPSD_USER = $DHCPSD_USER" echo diff --git a/src/Makefile b/src/Makefile index 554ab1e..4b9306a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -4,15 +4,16 @@ PROG= dhcpsd SRCS= dhcpsd.c common.c eloop.c if.c logerr.c SRCS+= bpf.c dhcp.c dhcp_lease.c SRCS+= if_none.c if_ether.c -SRCS+= service.c unpriv.c +SRCS+= service.c priv.c unpriv.c SRCS+= plugin.c -OBJS+= ${SRCS:.c=.o} +SRCS+= ${LINK_SRC} +OBJS+= ${SRCS:.c=.o} PVENDOR_SRCS= ${VENDOR_SRCS:vendor/%=${TOP}/vendor/%} -OBJS+= ${PVENDOR_SRCS:.c=.o} +OBJS+= ${PVENDOR_SRCS:.c=.o} PCOMPAT_SRCS= ${COMPAT_SRCS:compat/%=${TOP}/compat/%} -OBJS+= ${PCOMPAT_SRCS:.c=.o} +OBJS+= ${PCOMPAT_SRCS:.c=.o} CFLAGS?= -O2 SUBDIRS= plugins diff --git a/src/bpf.c b/src/bpf.c index b023b6d..24798d3 100644 --- a/src/bpf.c +++ b/src/bpf.c @@ -397,7 +397,7 @@ bpf_open(const struct interface *ifp, int (*filter)(const struct bpf *), #else struct bpf_version pv = { .bv_major = 0, .bv_minor = 0 }; struct ifreq ifr = { .ifr_flags = 0 }; -#if 0 +#if 1 int ibuf_len = 0; #endif unsigned int imm; @@ -506,7 +506,6 @@ bpf_open(const struct interface *ifp, int (*filter)(const struct bpf *), #ifdef __linux__ UNUSED(flags); #else -#if 0 if (flags & (O_RDONLY | O_RDWR)) { /* Get the required BPF buffer length from the kernel. */ if (ioctl(bpf->bpf_fd, BIOCGBLEN, &ibuf_len) == -1) @@ -517,7 +516,6 @@ bpf_open(const struct interface *ifp, int (*filter)(const struct bpf *), if (bpf->bpf_buffer == NULL) goto eexit; } -#endif #endif return bpf; @@ -529,7 +527,7 @@ bpf_open(const struct interface *ifp, int (*filter)(const struct bpf *), } /* Keep support for reading around, we might need it in the future. */ -#if 0 +#if 1 /* BPF requires that we read the entire buffer. * So we pass the buffer in the API so we can loop on >1 packet. */ ssize_t @@ -568,7 +566,8 @@ bpf_read(struct bpf *bpf, void *data, size_t len) bytes = (ssize_t)len; else bytes = (ssize_t)packet.bh_caplen; - memcpy(data, payload, (size_t)bytes); + if (data) + memcpy(data, payload, (size_t)bytes); next: bpf->bpf_pos += BPF_WORDALIGN( packet.bh_hdrlen + packet.bh_caplen); diff --git a/src/common.c b/src/common.c index b60f0a9..d7a6f45 100644 --- a/src/common.c +++ b/src/common.c @@ -414,7 +414,20 @@ inet_ntocidr(struct in_addr *addr) return cidr; } -size_t +int +sa_is_link(const struct sockaddr *sa) +{ +#ifdef AF_LINK + return sa->sa_family == AF_LINK ? 1 : 0; +#elif defined(AF_PACKET) + return sa->sa_family == AF_PACKET ? 1 : 0; +#else + errno = EAFNOSUPPORT; + return -1; +#endif +} + +socklen_t sa_len(const struct sockaddr *sa) { #ifdef BSD diff --git a/src/common.h b/src/common.h index f500dc0..fd075c0 100644 --- a/src/common.h +++ b/src/common.h @@ -100,7 +100,8 @@ uint8_t inet_ntocidr(struct in_addr *); struct sockaddr; -size_t sa_len(const struct sockaddr *); +int sa_is_link(const struct sockaddr *); +socklen_t sa_len(const struct sockaddr *); int sa_cmp(const struct sockaddr *, const struct sockaddr *); #define ss_len(ss) sa_len((const struct sockaddr *)(ss)) int sa_pton(struct sockaddr *, const char *restrict); diff --git a/src/dhcp.c b/src/dhcp.c index ace4354..8585613 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -38,6 +38,8 @@ #include +#include "src/priv.h" + #ifdef AF_LINK #include #endif @@ -110,8 +112,8 @@ static void dhcp_set_expire_timeout(struct dhcp_ctx *ctx); static void dhcp_handlebootp(struct interface *ifp, struct bootp *bootp, size_t len, uint32_t flags); -static inline int -dhcp_cookiecmp(struct bootp *bootp) +int +dhcp_cookiecmp(const struct bootp *bootp) { uint32_t cookie = htonl(MAGIC_COOKIE); @@ -259,7 +261,7 @@ dhcp_findoption(const struct bootp *bootp, size_t len, uint8_t opt) return NULL; } -static void +static ssize_t dhcp_outputudp(const struct interface *ifp, const size_t len) { const struct dhcp_ctx *ctx = ifp->if_ctx->ctx_dhcp; @@ -272,22 +274,28 @@ dhcp_outputudp(const struct interface *ifp, const size_t len) .sin_len = sizeof(sin), #endif }; + ssize_t nbytes; #ifdef HAVE_CASPER if (cap_connect(ctx->dhcp_ctx->ctx_capnet, ctx->dhcp_capfd, - (struct sockaddr *)&sin, sizeof(sin)) == -1) + (struct sockaddr *)&sin, sizeof(sin)) == -1) { logerr("%s: cap_connect", __func__); - else if (send(ctx->dhcp_capfd, bootp, len, 0) == -1) + return -1; + } + nbytes = send(ctx->dhcp_capfd, bootp, len, 0); + if (nbytes == -1) logerr("%s: send", __func__); #else - if (sendto(ctx->dhcp_fd, bootp, len, 0, (struct sockaddr *)&sin, - sizeof(sin)) == -1) + nbytes = sendto(ctx->dhcp_fd, bootp, len, 0, (struct sockaddr *)&sin, + sizeof(sin)); + if (nbytes == -1) logerr("%s: sendto", __func__); #endif + return nbytes; } -static void -dhcp_outputipudp(const struct interface *ifp, const struct in_addr *src, +static ssize_t +dhcp_outputipudp(struct interface *ifp, const struct in_addr *src, const size_t len) { const struct dhcp_ctx *ctx = ifp->if_ctx->ctx_dhcp; @@ -328,6 +336,7 @@ dhcp_outputipudp(const struct interface *ifp, const struct in_addr *src, .iov_len = len, }, }; + ssize_t nbytes; if (dhcp_cookiecmp(bootp) == 0 && ((opt = dhcp_findoption(bootp, len, DHO_MESSAGETYPE)) != NULL) && @@ -360,9 +369,10 @@ dhcp_outputipudp(const struct interface *ifp, const struct in_addr *src, in_cksum(&udp, sizeof(udp), &sum); udp.uh_sum = in_cksum(bootp, len, &sum); - if (ifp->if_output(ifp, ifp->if_bpf->bpf_fd, iov, ARRAYCOUNT(iov)) == - -1) - logerr("%s: if_output: %s", __func__, ifp->if_name); + nbytes = priv_sendbpf(ifp, iov, ARRAYCOUNT(iov)); + if (nbytes == -1) + logerr("%s: priv_sendbpf: %s", __func__, ifp->if_name); + return nbytes; } static const char * @@ -837,12 +847,12 @@ dhcp_addoptions(struct bootp *bootp, uint8_t **p, const uint8_t *e, return 0; } -static void -dhcp_output(const struct interface *ifp, const struct dhcp_lease *lease, +static ssize_t +dhcp_output(struct interface *ifp, const struct dhcp_lease *lease, const uint8_t type, const char *msg, const struct bootp *req, size_t reqlen) { struct dhcp_ctx *ctx = ifp->if_ctx->ctx_dhcp; - struct bootp *bootp = ctx->dhcp_bootp; + struct bootp *bootp; const struct dhcp_pool *pool; struct in_addr addr; uint8_t *p, *e; @@ -852,6 +862,18 @@ dhcp_output(const struct interface *ifp, const struct dhcp_lease *lease, const uint8_t *opt; int err; + len = (size_t)ifp->if_mtu - sizeof(struct udphdr) - sizeof(struct ip); + if (len > ctx->dhcp_bootplen) { + void *n = realloc(ctx->dhcp_bootp, len); + if (n == NULL) { + logerr("%s: realloc", __func__); + return -1; + } + ctx->dhcp_bootp = n; + ctx->dhcp_bootplen = len; + } + + bootp = ctx->dhcp_bootp; memset(bootp, 0, sizeof(*bootp)); bootp->op = BOOTREPLY; bootp->flags = htons(ntohs(req->flags) & BROADCAST_FLAG); @@ -909,7 +931,7 @@ dhcp_output(const struct interface *ifp, const struct dhcp_lease *lease, ifp->if_name); else { logerr("%s: dhcp_addoptions", ifp->if_name); - return; + return -1; } } @@ -928,10 +950,9 @@ dhcp_output(const struct interface *ifp, const struct dhcp_lease *lease, bootp->xid, inet_ntoa(addr)); if (bootp->giaddr != INADDR_ANY || (bootp->ciaddr != INADDR_ANY && type != DHCP_NAK)) - dhcp_outputudp(ifp, len); - else - dhcp_outputipudp(ifp, &pool->dp_addr, len); - return; + return dhcp_outputudp(ifp, len); + + return dhcp_outputipudp(ifp, &pool->dp_addr, len); } static void @@ -1644,7 +1665,7 @@ dhcp_readudp0(struct dhcp_ctx *ctx, int fd, unsigned short events) if (ifp == NULL) { /* This is a common situation for me when my tap * interfaces come and go. */ -#if 0 +#if 1 logwarnx("dhcp: interface not found 0x%x %s", bootp->xid, inet_ntoa(from.sin_addr)); #endif @@ -1709,54 +1730,6 @@ dhcp_expire_leases(struct dhcp_ctx *ctx) dhcp_set_expire_timeout(ctx); } -int -dhcp_openbpf(struct interface *ifp) -{ - struct ctx *ctx = ifp->if_ctx; - struct dhcp_ctx *dhcp_ctx = ctx->ctx_dhcp; - struct ifreq ifr = { .ifr_mtu = 0 }; - size_t buflen; -#ifdef HAVE_CASPER - cap_rights_t rights; -#endif - - /* Ensure our bootp write buffer is as big as the MTU */ - strlcpy(ifr.ifr_name, ifp->if_name, sizeof(ifr.ifr_name)); - if (ioctl(ctx->ctx_pf_inet_fd, SIOCGIFMTU, &ifr, sizeof(ifr)) == -1) { - logerr("%s SIOCGIFMTU", __func__); - return -1; - } - buflen = (size_t)ifr.ifr_mtu - sizeof(struct udphdr) - - sizeof(struct ip); - if (buflen > dhcp_ctx->dhcp_bootplen) { - void *n = realloc(dhcp_ctx->dhcp_bootp, buflen); - if (n == NULL) { - logerr("%s: realloc", __func__); - return -1; - } - dhcp_ctx->dhcp_bootp = n; - dhcp_ctx->dhcp_bootplen = buflen; - } - - /* - * We only write to BPF, we don't read as we get the - * same data from the UDP socket even for unconfigured clients. - */ - ifp->if_bpf = bpf_open(ifp, bpf_bootp, O_WRONLY); - if (ifp->if_bpf == NULL) - return -1; - -#ifdef HAVE_CASPER - cap_rights_init(&rights, CAP_WRITE); - if (caph_rights_limit(ifp->if_bpf->bpf_fd, &rights) == -1) { - logerr("%s: caph_rights_limit", __func__); - return -1; - } -#endif - - return 0; -} - static int dhcp_open(void) { @@ -1814,6 +1787,7 @@ dhcp_open(void) goto errexit; } + logdebugx("dhcp: listening on port %d", BOOTPS); return s; errexit: diff --git a/src/dhcp.h b/src/dhcp.h index 0da0f65..cc6a3f1 100644 --- a/src/dhcp.h +++ b/src/dhcp.h @@ -152,12 +152,15 @@ struct bootp { #define DHCP_TTL 128 #define DHCP_LEASE_TIME 3600 +struct interface; + struct dhcp_pool { struct in_addr dp_addr; struct in_addr dp_mask; struct in_addr dp_from; struct in_addr dp_to; uint32_t dp_lease_time; + struct interface *dp_ifp; }; #define DHCP_CLIENTID_LEN 1 + 255 /* first byte is length */ @@ -213,11 +216,8 @@ struct dhcp_ctx { #endif }; -struct interface; - struct dhcp_ctx *dhcp_new(struct ctx *); void dhcp_free(struct dhcp_ctx *); -int dhcp_openbpf(struct interface *); void dhcp_expire_leases(struct dhcp_ctx *ctx); struct dhcp_lease *dhcp_alloclease(void); @@ -226,6 +226,7 @@ struct dhcp_lease *dhcp_newleaseaddr(struct dhcp_ctx *, const struct dhcp_lease *); /* For plugins to interogate the DHCP message */ +int dhcp_cookiecmp(const struct bootp *); const uint8_t *dhcp_findoption(const struct bootp *, size_t, uint8_t); const char *dhcp_ftoa(uint32_t); diff --git a/src/dhcpsd.8.in b/src/dhcpsd.8.in index d022123..039b306 100644 --- a/src/dhcpsd.8.in +++ b/src/dhcpsd.8.in @@ -24,7 +24,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd July 25, 2025 +.Dd April 16, 2026 .Dt DHCPSD 8 .Os .Sh NAME @@ -32,7 +32,7 @@ .Nd a DHCP server .Sh SYNOPSIS .Nm -.Op Fl df +.Op Fl dfw .Op Fl p Ar plugin .Sh DESCRIPTION .Nm @@ -79,6 +79,14 @@ is appended and it is loaded from the plugin directory You may use the .Fl p option as many times as you have plugins. +.It Fl w +Waits until a message is received on an interface before configuring it. +This allows +.Nm +to work with dynamically created interfaces that are in the ready state. +If an interface is marked down or departs then +.Nm +will deactivate the interface. .El .Sh NOTES .Nm diff --git a/src/dhcpsd.c b/src/dhcpsd.c index 8b6c999..125cfcf 100644 --- a/src/dhcpsd.c +++ b/src/dhcpsd.c @@ -53,6 +53,7 @@ #include "if.h" #include "logerr.h" #include "plugin.h" +#include "priv.h" #include "queue.h" #include "service.h" #include "unpriv.h" @@ -95,10 +96,12 @@ dhcpsd_signal_cb(int sig, void *arg) #define SIGMSG "received %s, %s" switch (sig) { case SIGINT: - loginfox(SIGMSG, "SIGINT", "stopping"); + if (ctx->ctx_options & DHCPSD_MAIN) + loginfox(SIGMSG, "SIGINT", "stopping"); break; case SIGTERM: - loginfox(SIGMSG, "SIGTERM", "stopping"); + if (ctx->ctx_options & DHCPSD_MAIN) + loginfox(SIGMSG, "SIGTERM", "stopping"); exit_code = EXIT_SUCCESS; break; default: @@ -106,12 +109,17 @@ dhcpsd_signal_cb(int sig, void *arg) return; } + ctx->ctx_options |= DHCPSD_EXITING; eloop_exit(ctx->ctx_eloop, exit_code); } int dhcpsd_dropperms(int do_chroot) { +#ifdef ASAN + logwarnx("not dropping permissions due to ASAN"); + UNUSED(do_chroot); +#else struct passwd *pw; pw = getpwnam(DHCPSD_USER); @@ -141,6 +149,7 @@ dhcpsd_dropperms(int do_chroot) logerr("%s: error dropping privileges", __func__); return -1; } +#endif return 0; } @@ -293,6 +302,37 @@ dhcpsd_send_launcher(struct ctx *ctx, int exit_code) ctx->ctx_fork_fd = -1; } +ssize_t +dhcpsd_configure_pools(struct interface *ifp) +{ + struct ctx *ctx = ifp->if_ctx; + ssize_t npools = 0; + struct plugin *p; + + if (!(ifp->if_flags & IF_ACTIVE)) + return 0; + + PLUGIN_FOREACH(ctx, p) + { + if (p->p_configure_pools == NULL) + continue; + ssize_t n = p->p_configure_pools(p, ifp); + if (n == -1 && ctx->ctx_argc != 0) + return -1; + if (n == -1 || n == 0) + continue; + /* XXX When we grow DHCPv6 only open BPF if we configure + * a DHCPv4 pool. */ + if (priv_openbpf(ifp) == -1) + return -1; + /* First plugin with config wins */ + npools += n; + break; + } + + return npools; +} + int main(int argc, char **argv) { @@ -306,7 +346,8 @@ main(int argc, char **argv) .ctx_pf_link_fd = -1, #endif }; - int ch, exit_code = EXIT_FAILURE, f_flag = 0; + int ch, exit_code = EXIT_FAILURE; + bool f_flag = false; size_t npools; struct interface *ifp; unsigned int logopts = LOGERR_LOG; @@ -321,17 +362,22 @@ main(int argc, char **argv) closefrom(STDERR_FILENO + 1); logopts |= LOGERR_ERR; // log to stderr -#define OPTS "dfp:" +#define OPTS "dfp:w" while ((ch = getopt(argc, argv, OPTS)) != -1) { switch (ch) { case 'd': logopts |= LOGERR_DEBUG; break; case 'f': - f_flag = 1; + f_flag = true; logopts |= LOGERR_ERR; // log to stderr logopts &= ~LOGERR_LOG; // don't syslog break; + case 'w': + ctx.ctx_options |= DHCPSD_WAITIF; + break; + case '?': + goto exit; } } @@ -379,6 +425,20 @@ main(int argc, char **argv) argc -= optind; argv += optind; + ctx.ctx_argc = argc; + ctx.ctx_argv = argv; + + if (priv_init(&ctx) == NULL) { + logerr("%s: priv_init", __func__); + goto exit; + } + if (ctx.ctx_options & DHCPSD_RUN) + goto open_pf_inet; + + if (unpriv_init(&ctx) == NULL) { + logerr("%s: unpriv_init", __func__); + goto exit; + } if (ctx.ctx_plugins == NULL) { loginfox("no plugins specified"); @@ -386,14 +446,12 @@ main(int argc, char **argv) goto exit; if (plugin_load(&ctx, "leasefile") == -1) goto exit; - } else { - if (unpriv_init(&ctx) == NULL) - goto exit; } PLUGIN_FOREACH(&ctx, p) { - if (ctx.ctx_options & DHCPSD_UNPRIV && !p->p_unpriv) { + if (ctx.ctx_options & DHCPSD_PRIV || + (ctx.ctx_options & DHCPSD_UNPRIV && !p->p_unpriv)) { plugin_unload(p); continue; } @@ -406,14 +464,14 @@ main(int argc, char **argv) } } - if (ctx.ctx_options & DHCPSD_RUN) - goto run; - +open_pf_inet: ctx.ctx_pf_inet_fd = xsocket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (ctx.ctx_pf_inet_fd == -1) { logerr("%s: PF_INET", __func__); goto exit; } + if (ctx.ctx_options & DHCPSD_RUN) + goto run; #ifdef IFLR_ACTIVE ctx.ctx_pf_link_fd = xsocket(PF_LINK, SOCK_DGRAM | SOCK_CLOEXEC, 0); @@ -423,31 +481,34 @@ main(int argc, char **argv) } #endif - if (getifaddrs(&ctx.ctx_ifa) == -1) { - logerr("%s: getifaddrs", __func__); + if (link_open(&ctx) == -1) { + logerr("%s: link_open", __func__); goto exit; } - if_learnifaces(&ctx); - npools = 0; - TAILQ_FOREACH(ifp, ctx.ctx_ifaces, if_next) { - if (argc == 0) - ifp->if_flags |= IF_ACTIVE; - else { - for (ch = 0; ch < argc; ch++) { - if (strcmp(argv[ch], ifp->if_name) == 0) { - ifp->if_flags |= IF_ACTIVE; - argv[ch][0] = '\0'; - break; + if (!(ctx.ctx_options & DHCPSD_WAITIF)) { + if_learnifaces(&ctx); + npools = 0; + TAILQ_FOREACH(ifp, ctx.ctx_ifaces, if_next) { + if (argc == 0) + ifp->if_flags |= IF_ACTIVE; + else { + for (ch = 0; ch < argc; ch++) { + if (strcmp(argv[ch], ifp->if_name) == + 0) { + ifp->if_flags |= IF_ACTIVE; + argv[ch][0] = '\0'; + break; + } } } } - } - for (ch = 0; ch < argc; ch++) { - if (argv[ch][0] != '\0') { - logerrx("%s: no such interface", argv[ch]); - goto exit; + for (ch = 0; ch < argc; ch++) { + if (argv[ch][0] != '\0') { + logerrx("%s: no such interface", argv[ch]); + goto exit; + } } } @@ -464,33 +525,18 @@ main(int argc, char **argv) TAILQ_FOREACH(ifp, ctx.ctx_ifaces, if_next) { if (!(ifp->if_flags & IF_ACTIVE)) continue; - PLUGIN_FOREACH(&ctx, p) - { - if (p->p_configure_pools == NULL) - continue; - ssize_t n = p->p_configure_pools(p, ifp); - if (n == -1 && argc != 0) - goto exit; - if (n == -1 || n == 0) - continue; - /* XXX When we grow DHCPv6 only open BPF if we configure - * a DHCPv4 pool. */ - if (dhcp_openbpf(ifp) == -1) - goto exit; - /* First plugin with config wins */ - npools += (size_t)n; - break; - } + ssize_t n = dhcpsd_configure_pools(ifp); + if (n == -1 && argc != 0) + goto exit; + if (n == -1 || n == 0) + continue; + npools += (size_t)n; } - if (npools == 0) { + if (!(ctx.ctx_options & DHCPSD_WAITIF) && npools == 0) { logerrx("no pools, nothing to serve"); goto exit; } - /* May as well free this now */ - freeifaddrs(ctx.ctx_ifa); - ctx.ctx_ifa = NULL; - loginfox("dropping to user: %s", DHCPSD_USER); if (dhcpsd_dropperms(1) == -1) goto exit; @@ -603,8 +649,6 @@ main(int argc, char **argv) exit: plugin_unloadall(&ctx); - if (ctx.ctx_ifa) - freeifaddrs(ctx.ctx_ifa); while ((ifp = TAILQ_FIRST(ctx.ctx_ifaces)) != NULL) { TAILQ_REMOVE(ctx.ctx_ifaces, ifp, if_next); if_free(ifp); @@ -613,12 +657,14 @@ main(int argc, char **argv) eloop_free(ctx.ctx_eloop); ctx.ctx_eloop = NULL; srv_free(ctx.ctx_unpriv); + srv_free(ctx.ctx_priv); + link_free(&ctx); #ifdef HAVE_CASPER if (ctx.ctx_capnet) cap_close(ctx.ctx_capnet); #endif if (ctx.ctx_options & DHCPSD_MAIN) { - logdebugx("dhcpsd exited"); + loginfox("dhcpsd exited"); dhcpsd_send_launcher(&ctx, exit_code); } diff --git a/src/dhcpsd.h b/src/dhcpsd.h index acea3ef..b79f950 100644 --- a/src/dhcpsd.h +++ b/src/dhcpsd.h @@ -31,7 +31,7 @@ #include -struct ifaddrs; +struct link_ctx; struct dhcp_ctx; struct eloop; struct if_head; @@ -42,21 +42,27 @@ typedef struct cap_channel cap_channel_t; #endif struct ctx { - struct ifaddrs *ctx_ifa; + int ctx_argc; + char **ctx_argv; struct dhcp_ctx *ctx_dhcp; struct eloop *ctx_eloop; struct if_head *ctx_ifaces; + struct srv_ctx *ctx_priv; struct srv_ctx *ctx_unpriv; struct plugin *ctx_plugins; size_t ctx_nplugins; unsigned int ctx_options; #define DHCPSD_RUN (1U << 0) /* Set by forked stuff */ -#define DHCPSD_MAIN (1U << 1) /* Main process */ -#define DHCPSD_UNPRIV (1U << 2) /* Unprivileged helper */ -#define DHCPSD_LAUNCHER (1U << 3) /* Launcher process */ +#define DHCPSD_EXITING (1U << 1) /* process will exit */ +#define DHCPSD_MAIN (1U << 2) /* Main process */ +#define DHCPSD_PRIV (1U << 3) /* Privileged helper */ +#define DHCPSD_UNPRIV (1U << 4) /* Unprivileged helper */ +#define DHCPSD_LAUNCHER (1U << 5) /* Launcher process */ +#define DHCPSD_WAITIF (1U << 6) /* wait for interfaces */ + struct link_ctx *ctx_link; int ctx_fork_fd; int ctx_pf_inet_fd; #ifdef IFLR_ACTIVE @@ -67,6 +73,16 @@ struct ctx { #endif }; +struct interface; + int dhcpsd_dropperms(int); +ssize_t dhcpsd_configure_pools(struct interface *); + +/* + * This is defined by the OS specific link mechanism: + * route(4) or rtnetlink(7) + */ +int link_open(struct ctx *); +void link_free(struct ctx *); #endif /* CTX_H */ diff --git a/src/if.c b/src/if.c index ed16ecb..1ff8da5 100644 --- a/src/if.c +++ b/src/if.c @@ -33,6 +33,14 @@ #include #include #include + +#include + +#include "queue.h" +#include "src/common.h" +#include "src/priv.h" +#include "src/unpriv.h" + #ifdef AF_LINK #include #include @@ -43,6 +51,7 @@ #include #endif +#include #include #include #include @@ -58,121 +67,178 @@ #include "if_none.h" #include "logerr.h" -int -if_learnifaces(struct ctx *ctx) +void +if_update_output(struct interface *ifp) { - struct ifaddrs *ifa; - struct interface *ifp; -#ifdef AF_LINK - struct sockaddr_dl *sdl; -#ifdef IFLR_ACTIVE - struct if_laddrreq iflr = { .flags = IFLR_PREFIX }; -#endif -#elif defined(AF_PACKET) - struct sockaddr_ll *sll; -#endif + switch (ifp->if_hwtype) { + case ARPHRD_ETHER: + ifp->if_output = if_ether_output; + break; + default: + ifp->if_output = if_none_output; + break; + } +} - for (ifa = ctx->ctx_ifa; ifa; ifa = ifa->ifa_next) { - if (ifa->ifa_addr == NULL) - continue; +void +if_update(struct interface *ifp, struct sockaddr *sa) +{ #ifdef AF_LINK - if (ifa->ifa_addr->sa_family != AF_LINK) - continue; -#elif defined(AF_PACKET) - if (ifa->ifa_addr->sa_family != AF_PACKET) - continue; -#endif - -#ifdef IFLR_ACTIVE - sdl = (void *)ifa->ifa_addr; - - /* We need to check for active address */ - strlcpy(iflr.iflr_name, ifa->ifa_name, sizeof(iflr.iflr_name)); - memcpy(&iflr.addr, ifa->ifa_addr, - MIN(ifa->ifa_addr->sa_len, sizeof(iflr.addr))); - iflr.flags = IFLR_PREFIX; - iflr.prefixlen = (unsigned int)sdl->sdl_alen * NBBY; - if (ioctl(ctx->ctx_pf_link_fd, SIOCGLIFADDR, &iflr) == -1 || - !(iflr.flags & IFLR_ACTIVE)) - continue; -#endif - - ifp = calloc(1, sizeof(*ifp)); - if (ifp == NULL) { - logerr("%s: malloc", __func__); - return -1; - } - ifp->if_ctx = ctx; - strlcpy(ifp->if_name, ifa->ifa_name, sizeof(ifp->if_name)); + struct sockaddr_dl *sdl = (void *)sa; -#ifdef AF_LINK -#ifndef IFLR_ACTIVE - sdl = (void *)ifa->ifa_addr; -#endif + ifp->if_index = sdl->sdl_index; - switch (sdl->sdl_type) { + switch (sdl->sdl_type) { #ifdef IFT_BRIDGE - case IFT_BRIDGE: /* FALLTHROUGH */ + case IFT_BRIDGE: /* FALLTHROUGH */ #endif #ifdef IFT_PROPVIRTUAL - case IFT_PROPVIRTUAL: /* FALLTHROUGH */ + case IFT_PROPVIRTUAL: /* FALLTHROUGH */ #endif #ifdef IFT_TUNNEL - case IFT_TUNNEL: /* FALLTHROUGH */ + case IFT_TUNNEL: /* FALLTHROUGH */ #endif - case IFT_LOOP: /* FALLTHROUGH */ - case IFT_PPP: /* FALLTHROUGH */ + case IFT_LOOP: /* FALLTHROUGH */ + case IFT_PPP: /* FALLTHROUGH */ #ifdef IFT_L2VLAN - case IFT_L2VLAN: /* FALLTHROUGH */ + case IFT_L2VLAN: /* FALLTHROUGH */ #endif #ifdef IFT_L3IPVLAN - case IFT_L3IPVLAN: /* FALLTHROUGH */ + case IFT_L3IPVLAN: /* FALLTHROUGH */ #endif - case IFT_ETHER: - ifp->if_hwtype = ARPHRD_ETHER; - break; + case IFT_ETHER: + ifp->if_hwtype = ARPHRD_ETHER; + break; #ifdef notyet #ifdef IFT_IEEE1394 - case IFT_IEEE1394: - ifp->if_hwtype = ARPHRD_IEEE1394; - break; + case IFT_IEEE1394: + ifp->if_hwtype = ARPHRD_IEEE1394; + break; #endif #ifdef IFT_INFINIBAND - case IFT_INFINIBAND: - ifp->if_hwtype = ARPHRD_INFINIBAND; - break; + case IFT_INFINIBAND: + ifp->if_hwtype = ARPHRD_INFINIBAND; + break; #endif #endif - default: - logdebugx("%s: unsupported interface type 0x%.2x", - ifp->if_name, sdl->sdl_type); - break; - } - ifp->if_index = sdl->sdl_index; + default: + logdebugx("%s: unsupported interface type 0x%.2x", ifp->if_name, + sdl->sdl_type); + break; + } + + if (sdl->sdl_alen <= sizeof(ifp->if_hwaddr)) { ifp->if_hwlen = sdl->sdl_alen; - if (ifp->if_hwlen != 0) - memcpy(ifp->if_hwaddr, LLADDR(sdl), ifp->if_hwlen); + memcpy(ifp->if_hwaddr, LLADDR(sdl), sdl->sdl_alen); + } #elif defined(AF_PACKET) - sll = (void *)ifa->ifa_addr; - ifp->if_index = (unsigned int)sll->sll_ifindex; - ifp->if_hwtype = sll->sll_hatype; + struct sockaddr_ll *sll = (void *)sa; + ifp->if_index = (unsigned int)sll->sll_ifindex; + ifp->if_hwtype = sll->sll_hatype; + if (ifp->if_hwlen <= sizeof(ifp->if_hwaddr)) { ifp->if_hwlen = sll->sll_halen; - if (ifp->if_hwlen != 0) - memcpy(ifp->if_hwaddr, sll->sll_addr, ifp->if_hwlen); + memcpy(ifp->if_hwaddr, sll->sll_addr, ifp->if_hwlen); + } #endif - switch (ifp->if_hwtype) { - case ARPHRD_ETHER: - ifp->if_output = if_ether_output; - break; - default: - ifp->if_output = if_none_output; - break; +} + +int +if_update_mtu(struct interface *ifp) +{ + struct ctx *ctx = ifp->if_ctx; + struct ifreq ifr = { .ifr_mtu = 0 }; + + strlcpy(ifr.ifr_name, ifp->if_name, sizeof(ifr.ifr_name)); + if (ioctl(ctx->ctx_pf_inet_fd, SIOCGIFMTU, &ifr, sizeof(ifr)) == -1) { + logerr("%s SIOCGIFMTU", __func__); + return -1; + } + + ifp->if_mtu = ifr.ifr_mtu; + return 0; +} + +int +if_sockaddr_active(struct ctx *ctx, const char *if_name, + const struct sockaddr *sa) +{ +#ifdef IFLR_ACTIVE + const struct sockaddr_dl *sdl = (const void *)sa; + struct if_laddrreq iflr = { .flags = IFLR_PREFIX }; + + strlcpy(iflr.iflr_name, ifa->ifa_name, sizeof(iflr.iflr_name)); + memcpy(&iflr.addr, ifa->ifa_addr, + MIN(ifa->ifa_addr->sa_len, sizeof(iflr.addr))); + iflr.flags = IFLR_PREFIX; + iflr.prefixlen = (unsigned int)sdl->sdl_alen * NBBY; + if (ioctl(ctx->ctx_pf_link_fd, SIOCGLIFADDR, &iflr) == -1 || + !(iflr.flags & IFLR_ACTIVE)) + return 0; +#else + UNUSED(ctx); + UNUSED(if_name); + UNUSED(sa); +#endif + + return 1; +} + +int +if_link_match_index(const struct sockaddr *sa, unsigned int if_index) +{ + if (sa_is_link(sa) != 1) { + errno = EINVAL; + return -1; + } + +#ifdef AF_LINK + const struct sockaddr_dl *sdl = (const void *)sa; + return sdl->sdl_index == if_index ? 1 : 0; +#elif defined(AF_PACKET) + const struct sockaddr_ll *sll = (const void *)ifa->ifa_addr; + return (unsigned int)sll->sll_index == if_index ? 1 : 0; +#endif +} + +int +if_learnifaces(struct ctx *ctx) +{ + struct ifaddrs *ifaddrs, *ifa; + struct interface *ifp; + int err = -1; + + if (unpriv_getifaddrs(ctx->ctx_unpriv, &ifaddrs, NULL) == -1) { + logerr("%s: unpriv_getifaddrs", __func__); + return -1; + } + for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) + continue; + if (!sa_is_link(ifa->ifa_addr)) + continue; + + if (if_sockaddr_active(ctx, ifa->ifa_name, ifa->ifa_addr) != 1) + continue; + + ifp = calloc(1, sizeof(*ifp)); + if (ifp == NULL) { + logerr("%s: malloc", __func__); + goto err; } + ifp->if_ctx = ctx; + strlcpy(ifp->if_name, ifa->ifa_name, sizeof(ifp->if_name)); + + if_update(ifp, ifa->ifa_addr); + if_update_output(ifp); TAILQ_INSERT_TAIL(ctx->ctx_ifaces, ifp, if_next); } - return 0; + err = 0; + +err: + freeifaddrs(ifaddrs); + return err; } struct interface * @@ -182,7 +248,7 @@ if_findifpfromcmsg(struct ctx *ctx, struct msghdr *msg, void *to) unsigned int if_index = 0; struct interface *ifp; #ifdef IP_RECVIF - struct sockaddr_dl sdl; + struct sockaddr_dl sdl = { .sdl_len = 0 }; #else struct in_pktinfo ipi; #endif @@ -246,21 +312,81 @@ if_findifpfromcmsg(struct ctx *ctx, struct msghdr *msg, void *to) /* Find the receiving interface */ TAILQ_FOREACH(ifp, ctx->ctx_ifaces, if_next) { if (ifp->if_index == if_index) - return ifp; + break; + } + + if (ifp == NULL) { + int i; + + if (!(ctx->ctx_options & DHCPSD_WAITIF)) { + errno = ESRCH; + return NULL; + } + + ifp = calloc(1, sizeof(*ifp)); + if (ifp == NULL) + return NULL; + ifp->if_ctx = ctx; + ifp->if_index = if_index; + + if (unpriv_learnif(ifp) == -1) { + logerr("%s: unpriv_learnif", __func__); + if_free(ifp); + return NULL; + } + + if (ctx->ctx_argc == 0) + ifp->if_flags |= IF_ACTIVE; + else { + for (i = 0; i < ctx->ctx_argc; i++) { + if (strcmp(ifp->if_name, ctx->ctx_argv[i]) == + 0) { + ifp->if_flags |= IF_ACTIVE; + } + } + } + if (ifp->if_flags & IF_ACTIVE) { + log_infox("%s: activated interface (%d)", ifp->if_name, + ifp->if_index); + if (dhcpsd_configure_pools(ifp) == -1) { + logerr("%s: dhcpsd_configure_pools", + ifp->if_name); + if_free(ifp); + return NULL; + } + } + TAILQ_INSERT_TAIL(ctx->ctx_ifaces, ifp, if_next); } - /* No support for learning new interfaces after we have loaded. */ - errno = ESRCH; return ifp; } void if_free(struct interface *ifp) { + bool srv_if_free; + unsigned int options; + if (ifp == NULL) return; + + /* If we are exiting there is no need to notify our services + * that we are freeing an interface. */ + options = ifp->if_ctx->ctx_options; + srv_if_free = options & DHCPSD_MAIN && !(options & DHCPSD_EXITING); + + if (srv_if_free) { + if (options & DHCPSD_WAITIF) + loginfox("%s: deactiving interface", ifp->if_name); + if (priv_freeif(ifp) == -1 && errno != ESRCH) + logerr("%s: priv_freeif: %s", __func__, ifp->if_name); + if (unpriv_freeif(ifp) == -1 && errno != ESRCH) + logerr("%s: unpriv_freeif: %s", __func__, ifp->if_name); + } if (ifp->if_bpf != NULL) bpf_close(ifp->if_bpf); free(ifp->if_pools); + if (srv_if_free && options & DHCPSD_WAITIF) + logdebugx("%s: deactivated interface", ifp->if_name); free(ifp); } diff --git a/src/if.h b/src/if.h index 5bcf762..efd8de8 100644 --- a/src/if.h +++ b/src/if.h @@ -72,6 +72,11 @@ TAILQ_HEAD(if_head, interface); struct ifaddrs; +int if_sockaddr_active(struct ctx *, const char *, const struct sockaddr *); +int if_link_match_index(const struct sockaddr *, unsigned int); +void if_update(struct interface *, struct sockaddr *); +int if_update_mtu(struct interface *); +void if_update_output(struct interface *); int if_learnifaces(struct ctx *); void if_free(struct interface *); struct interface *if_findifpfromcmsg(struct ctx *, struct msghdr *, void *); diff --git a/src/if_ether.c b/src/if_ether.c index 0b80250..359f3ff 100644 --- a/src/if_ether.c +++ b/src/if_ether.c @@ -57,6 +57,7 @@ if_ether_output(const struct interface *ifp, int fd, const struct iovec *_iov, errno = EINVAL; return -1; } + memcpy(eh.ether_shost, ifp->if_hwaddr, ifp->if_hwlen); if (ip->ip_p != IPPROTO_UDP || ntohs(bootp->flags) & BROADCAST_FLAG) memset(eh.ether_dhost, 0xff, sizeof(eh.ether_dhost)); diff --git a/src/plugins/auto.c b/src/plugins/auto.c index b5af136..4991288 100644 --- a/src/plugins/auto.c +++ b/src/plugins/auto.c @@ -44,6 +44,7 @@ #include "if.h" #include "logerr.h" #include "plugin.h" +#include "unpriv.h" static const char auto_name[] = "auto"; static const char auto_description[] = "Automatic DHCP configuration"; @@ -65,7 +66,8 @@ inet_private_address(struct in_addr *addr) static ssize_t auto_configure_pools(__unused struct plugin *p, struct interface *ifp) { - struct ifaddrs *ifa; + struct srv_ctx *sctx = ifp->if_ctx->ctx_unpriv; + struct ifaddrs *ifaddrs, *ifa; struct sockaddr_in *sin_addr, *sin_mask; uint8_t *ip, cidr; struct dhcp_pool *pool; @@ -73,7 +75,11 @@ auto_configure_pools(__unused struct plugin *p, struct interface *ifp) const char *fromp, *top; ssize_t npools = 0; - for (ifa = ifp->if_ctx->ctx_ifa; ifa; ifa = ifa->ifa_next) { + if (unpriv_getifaddrs(sctx, &ifaddrs, &ifp->if_index) == -1) { + logerr("%s: unpriv_getifaddrs %d:", __func__, ifp->if_index); + return -1; + } + for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) { if (ifa->ifa_addr == NULL) continue; if (ifa->ifa_flags & IFF_LOOPBACK) @@ -113,6 +119,7 @@ auto_configure_pools(__unused struct plugin *p, struct interface *ifp) sizeof(*pool) * (ifp->if_npools + 1)); if (pool == NULL) { logerr("%s: realloc", __func__); + free(ifaddrs); return -1; } ifp->if_pools = pool; @@ -146,6 +153,7 @@ auto_configure_pools(__unused struct plugin *p, struct interface *ifp) npools++; } + freeifaddrs(ifaddrs); return npools; } diff --git a/src/priv.c b/src/priv.c new file mode 100644 index 0000000..610361a --- /dev/null +++ b/src/priv.c @@ -0,0 +1,258 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpsd - privileged service helper + * Copyright (c) 2025 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "bpf.h" +#include "common.h" +#include "dhcpsd.h" +#include "if.h" +#include "logerr.h" +#include "plugin.h" +#include "priv.h" +#include "queue.h" +#include "service.h" +#include "src/eloop.h" + +#define P_OPENBPF 1 +#define P_SENDBPF 2 +#define P_FREEIF 3 + +int +priv_openbpf(struct interface *ifp) +{ + struct srv_ctx *sctx = ifp->if_ctx->ctx_priv; + ssize_t result; + int err; + + err = srv_run(sctx, NULL, P_OPENBPF, ifp, sizeof(*ifp), &result, NULL, + 0); + return err == -1 ? -1 : (int)result; +} + +ssize_t +priv_sendbpf(struct interface *ifp, const struct iovec *iov, size_t iov_len) +{ + assert(iov_len == 3); + struct srv_ctx *sctx = ifp->if_ctx->ctx_priv; + struct iovec iov0[] = { + { .iov_base = UNCONST(&ifp->if_index), sizeof(ifp->if_index) }, + { .iov_base = iov[0].iov_base, .iov_len = iov[0].iov_len }, + { .iov_base = iov[1].iov_base, .iov_len = iov[1].iov_len }, + { .iov_base = iov[2].iov_base, .iov_len = iov[2].iov_len }, + }; + ssize_t result; + int err; + + err = srv_runv(sctx, NULL, P_SENDBPF, iov0, ARRAYCOUNT(iov0), &result, + NULL, 0); + return err == -1 ? -1 : (int)result; +} + +int +priv_freeif(struct interface *ifp) +{ + struct srv_ctx *sctx = ifp->if_ctx->ctx_priv; + ssize_t result; + int err; + + err = srv_run(sctx, NULL, P_FREEIF, &ifp->if_index, + sizeof(ifp->if_index), &result, NULL, 0); + return err == -1 ? -1 : (int)result; +} + +static ssize_t +priv_dispatch_openbpf(struct srv_ctx *sctx, const void *data, size_t len) +{ + struct ctx *ctx = sctx->srv_ctx; + const struct interface *iff = data; + struct interface *ifp; + ssize_t err = -1; + + if (len != sizeof(*iff)) { + errno = EINVAL; + goto err; + } + + TAILQ_FOREACH(ifp, ctx->ctx_ifaces, if_next) { + if (ifp->if_index == iff->if_index) + break; + } + if (ifp == NULL) { + ifp = calloc(1, sizeof(*ifp)); + ifp->if_ctx = ctx; + ifp->if_index = iff->if_index; + if (strlcpy(ifp->if_name, iff->if_name, sizeof(ifp->if_name)) > + sizeof(iff->if_name)) { + free(ifp); + errno = EINVAL; + goto err; + } + ifp->if_flags = iff->if_flags; + ifp->if_hwtype = iff->if_hwtype; + if (iff->if_hwlen > sizeof(ifp->if_hwaddr)) { + free(ifp); + errno = EINVAL; + goto err; + } + ifp->if_hwlen = iff->if_hwlen; + memcpy(ifp->if_hwaddr, iff->if_hwaddr, sizeof(iff->if_hwaddr)); + if_update_output(ifp); + TAILQ_INSERT_TAIL(ctx->ctx_ifaces, ifp, if_next); + } + + if (ifp->if_bpf == NULL) { + /* + * We only write to BPF, we don't read as we get the + * same data from the UDP socket even for unconfigured clients. + */ + ifp->if_bpf = bpf_open(ifp, bpf_bootp, O_WRONLY); + if (ifp->if_bpf != NULL) + err = 0; + } + +err: + return srv_send(sctx, NULL, P_OPENBPF, err, NULL, 0); +} + +static ssize_t +priv_dispatch_sendbpf(struct srv_ctx *sctx, const void *data, size_t len) +{ + struct ctx *ctx = sctx->srv_ctx; + unsigned int if_index; + struct interface *ifp; + struct iovec iov[] = { { .iov_base = (uint8_t *)UNCONST(data) + + sizeof(if_index), + .iov_len = len - sizeof(if_index) } }; + ssize_t err = -1; + + if (len <= sizeof(if_index)) { + errno = EINVAL; + goto err; + } + memcpy(&if_index, data, sizeof(if_index)); + len -= sizeof(if_index); + + TAILQ_FOREACH(ifp, ctx->ctx_ifaces, if_next) { + if (ifp->if_index == if_index) + break; + } + if (ifp == NULL) { + errno = ESRCH; + goto err; + } + + err = ifp->if_output(ifp, ifp->if_bpf->bpf_fd, iov, ARRAYCOUNT(iov)); + +err: + return srv_send(sctx, NULL, P_SENDBPF, err, NULL, 0); +} + +static ssize_t +priv_dispatch_freeif(struct srv_ctx *sctx, const void *data, size_t len) +{ + struct ctx *ctx = sctx->srv_ctx; + unsigned int if_index; + struct interface *ifp; + ssize_t err = -1; + + if (len != sizeof(if_index)) { + errno = EINVAL; + goto err; + } + memcpy(&if_index, data, sizeof(if_index)); + + TAILQ_FOREACH(ifp, ctx->ctx_ifaces, if_next) { + if (ifp->if_index == if_index) + break; + } + if (ifp == NULL) { + errno = ESRCH; + goto err; + } + + TAILQ_REMOVE(ctx->ctx_ifaces, ifp, if_next); + if_free(ifp); + err = 0; + +err: + return srv_send(sctx, NULL, P_FREEIF, err, NULL, 0); +} + +static ssize_t +priv_dispatch(struct srv_ctx *sctx, struct plugin *p, unsigned int cmd, + const void *data, size_t len) +{ + if (p != NULL) { + if (p->p_dispatch != NULL) + return p->p_dispatch(p, sctx, cmd, data, len); + errno = ENOSYS; + logerr(__func__); + return srv_send(sctx, NULL, cmd, -1, NULL, 0); + } + + switch (cmd) { + case P_OPENBPF: + return priv_dispatch_openbpf(sctx, data, len); + case P_SENDBPF: + return priv_dispatch_sendbpf(sctx, data, len); + case P_FREEIF: + return priv_dispatch_freeif(sctx, data, len); + default: + errno = EINVAL; + logerr(__func__); + return srv_send(sctx, NULL, cmd, -1, NULL, 0); + } +} + +struct srv_ctx * +priv_init(struct ctx *ctx) +{ + if (ctx->ctx_priv != NULL) + goto out; + ctx->ctx_priv = srv_init(ctx, "privileged helper", priv_dispatch); + + if (ctx->ctx_priv == NULL) + return NULL; + + if (ctx->ctx_options & DHCPSD_RUN) { + ctx->ctx_options |= DHCPSD_PRIV; +#ifdef HAVE_SETPROCTITLE + setproctitle("privileged helper"); +#endif + } + +out: + return ctx->ctx_priv; +} diff --git a/src/priv.h b/src/priv.h new file mode 100644 index 0000000..c2e8b15 --- /dev/null +++ b/src/priv.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpsd - privileged service helper + * Copyright (c) 2025 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef PRIV_H +#define PRIV_H + +struct ctx; +struct srv_ctx; +struct interface; +struct iovec; + +struct srv_ctx *priv_init(struct ctx *); +int priv_openbpf(struct interface *); +ssize_t priv_sendbpf(struct interface *, const struct iovec *, size_t); +int priv_freeif(struct interface *); +#endif diff --git a/src/route.c b/src/route.c new file mode 100644 index 0000000..ffbcccd --- /dev/null +++ b/src/route.c @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpsd - route(4) supprt + * Copyright (c) 2025 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include + +#include +#include + +#include +#include +#include + +#include "common.h" +#include "dhcpsd.h" +#include "eloop.h" +#include "if.h" +#include "logerr.h" +#include "queue.h" + +struct link_ctx { + struct ctx *link_ctx; + int link_fd; +}; + +struct rtm { + struct rt_msghdr hdr; + char buffer[sizeof(struct sockaddr_storage) * RTAX_MAX]; +}; + +static void +route_dispatch_ifinfo(struct link_ctx *lctx, struct rt_msghdr *rtm) +{ + struct ctx *ctx = lctx->link_ctx; + struct if_msghdr *ifi = (struct if_msghdr *)rtm; + struct interface *ifp; + + if (rtm->rtm_msglen < sizeof(*ifi)) + return; + + if (ifi->ifm_flags & IFF_UP) + return; + + /* Interface is down, remove it from consideration. */ + TAILQ_FOREACH(ifp, ctx->ctx_ifaces, if_next) { + if (ifp->if_index == ifi->ifm_index) + break; + } + if (ifp == NULL) + return; + + logwarnx("%s: interface is down", ifp->if_name); + TAILQ_REMOVE(ctx->ctx_ifaces, ifp, if_next); + if_free(ifp); +} + +static void +route_dispatch(void *arg, unsigned short e) +{ + struct link_ctx *lctx = arg; + struct rtm rtm; + ssize_t len; + + if (e != ELE_READ) { + logerrx("%s: unexpeced event %d", __func__, e); + } + len = read(lctx->link_fd, &rtm, sizeof(rtm)); + if (len == -1) { + logerr("%s: read", __func__); + return; + } + + if ((size_t)len < offsetof(struct rt_msghdr, rtm_type) + + sizeof(rtm.hdr.rtm_type) || + len < rtm.hdr.rtm_msglen) { + logerrx("%s: truncated route message %zd %zu %d", __func__, len, + sizeof(rtm.hdr), rtm.hdr.rtm_msglen); + return; + } + + switch (rtm.hdr.rtm_type) { + case RTM_IFINFO: + route_dispatch_ifinfo(lctx, &rtm.hdr); + break; + default: + /* Ignore other messages */ + break; + } +} + +int +link_open(struct ctx *ctx) +{ + struct link_ctx *lctx = malloc(sizeof(*lctx)); +#ifdef SO_RERROR + int n; +#endif +#if defined(RO_MSGFILTER) || defined(ROUTE_MSGFILTER) + unsigned char msgfilter[] = { + RTM_IFINFO, +#ifdef RTM_IFANNOUNCE + RTM_IFANNOUNCE, +#endif + }; +#ifdef ROUTE_MSGFILTER + unsigned int i, msgfilter_mask; +#endif +#endif + + if (lctx == NULL) + return -1; + + lctx->link_ctx = ctx; + + lctx->link_fd = xsocket(PF_ROUTE, SOCK_RAW | SOCK_CXNB, AF_UNSPEC); + if (lctx->link_fd == -1) + return -1; + +#ifdef SO_RERROR + n = 1; + if (setsockopt(ctx->link_fd, SOL_SOCKET, SO_RERROR, &n, sizeof(n)) == + -1) + logerr("%s: SO_RERROR", __func__); +#endif + +#if defined(RO_MSGFILTER) + if (setsockopt(ctx->link_fd, PF_ROUTE, RO_MSGFILTER, &msgfilter, + sizeof(msgfilter)) == -1) + logerr(__func__); +#elif defined(ROUTE_MSGFILTER) + /* Convert the array into a bitmask. */ + msgfilter_mask = 0; + for (i = 0; i < __arraycount(msgfilter); i++) + msgfilter_mask |= ROUTE_FILTER(msgfilter[i]); + if (setsockopt(ctx->link_fd, PF_ROUTE, ROUTE_MSGFILTER, &msgfilter_mask, + sizeof(msgfilter_mask)) == -1) + logerr(__func__); +#else +#warning kernel does not support route message filtering +#endif + + if (eloop_event_add(ctx->ctx_eloop, lctx->link_fd, ELE_READ, + route_dispatch, lctx) == -1) { + logerr("%s: eloop_event_add", __func__); + return -1; + } + + return 0; +} + +void +link_free(struct ctx *ctx) +{ + struct link_ctx *lctx = ctx->ctx_link; + + if (lctx == NULL) + return; + if (lctx->link_fd != -1) + close(lctx->link_fd); + free(lctx); + ctx->ctx_link = NULL; +} \ No newline at end of file diff --git a/src/service.c b/src/service.c index 6d47145..d1588a3 100644 --- a/src/service.c +++ b/src/service.c @@ -67,6 +67,11 @@ srv_recv(struct srv_ctx *sctx, unsigned short e) if (e & ELE_HANGUP) { hangup: + if (sctx->srv_ctx->ctx_options & DHCPSD_MAIN) + logdebugx("%s(%d): fd %d hungup", __func__, getpid(), + sctx->srv_fd); + close(sctx->srv_fd); + sctx->srv_fd = -1; eloop_exit(sctx->srv_ctx->ctx_eloop, EXIT_SUCCESS); return -1; } @@ -204,8 +209,10 @@ srv_runv(struct srv_ctx *sctx, struct plugin *p, unsigned int cmd, } events = eloop_waitfd(sctx->srv_fd); - if (events == -1) + if (events == -1) { + logerr("%s: eloop_waitfd: %d", __func__, sctx->srv_fd); return -1; + } if (srv_recv(sctx, (unsigned short)events) == -1) return -1; @@ -277,7 +284,8 @@ srv_init(struct ctx *ctx, const char *name, default: sctx->srv_fd = fdset[0]; close(fdset[1]); - logdebugx("service: spawned %s on pid %ld", name, (long)pid); + logdebugx("service: spawned %s on pid %ld fd %d", name, + (long)pid, sctx->srv_fd); return sctx; } diff --git a/src/unpriv.c b/src/unpriv.c index b3e1d39..4b9b632 100644 --- a/src/unpriv.c +++ b/src/unpriv.c @@ -30,8 +30,25 @@ #include #include +#include + +#include "queue.h" + +#ifdef AF_LINK +#include +#include +#include +#undef AF_PACKET /* Newer Illumos defines this */ +#endif +#ifdef AF_PACKET +#include +#endif + #include +#include #include +#include +#include #include #include #include @@ -39,12 +56,18 @@ #include "common.h" #include "dhcpsd.h" +#include "if.h" #include "logerr.h" #include "plugin.h" #include "service.h" #include "unpriv.h" -#define U_GETADDRINFO 1 +#define U_GETIFADDRS 1 +#define U_LEARNIF 2 +#define U_FREEIF 3 +#define U_GETADDRINFO 4 + +#define IFA_NADDRS 4 struct unpriv_addrinfo { int u_ai_flags; @@ -62,6 +85,147 @@ struct unpriv_getaddrinfo { struct unpriv_addrinfo u_gai_hints; }; +int +unpriv_getifaddrs(struct srv_ctx *ctx, struct ifaddrs **ifahead, + const unsigned int *match_if_index) +{ + struct ifaddrs *ifa; + char *bp, *sap; + socklen_t salen; + ssize_t result; + void *rdata, *buf; + size_t rdata_len, len; + int err; + + err = srv_run(ctx, 0, U_GETIFADDRS, match_if_index, + match_if_index != NULL ? sizeof(*match_if_index) : 0, &result, + &rdata, &rdata_len); + if (err == -1) + return -1; + + /* Should be impossible - lo0 will always exist. */ + if (rdata_len == 0) { + *ifahead = NULL; + return 0; + } + + /* Take our own copy of the data. */ + buf = malloc(rdata_len); + if (buf == NULL) + return -1; + memcpy(buf, rdata, rdata_len); + len = rdata_len; + + bp = buf; + *ifahead = (struct ifaddrs *)(void *)bp; + for (ifa = *ifahead; ifa != NULL; ifa = ifa->ifa_next) { + if (len < ALIGN(sizeof(*ifa)) + ALIGN(IFNAMSIZ) + + ALIGN(sizeof(salen) * IFA_NADDRS)) + goto err; + bp += ALIGN(sizeof(*ifa)); + ifa->ifa_name = bp; + bp += ALIGN(IFNAMSIZ); + sap = bp; + bp += ALIGN(sizeof(salen) * IFA_NADDRS); + len -= ALIGN(sizeof(*ifa)) + ALIGN(IFNAMSIZ) + + ALIGN(sizeof(salen) * IFA_NADDRS); + +#define COPYOUTSA(addr) \ + do { \ + memcpy(&salen, sap, sizeof(salen)); \ + if (len < salen) \ + goto err; \ + if (salen != 0) { \ + (addr) = (struct sockaddr *)(void *)bp; \ + bp += ALIGN(salen); \ + len -= ALIGN(salen); \ + } \ + sap += sizeof(salen); \ + } while (0 /* CONSTCOND */) + + COPYOUTSA(ifa->ifa_addr); + COPYOUTSA(ifa->ifa_netmask); + COPYOUTSA(ifa->ifa_broadaddr); + + memcpy(&salen, sap, sizeof(salen)); + if (len < salen) + goto err; + if (salen != 0) { + ifa->ifa_data = bp; + bp += ALIGN(salen); + len -= ALIGN(salen); + } else + ifa->ifa_data = NULL; + + if (len != 0) + ifa->ifa_next = (struct ifaddrs *)(void *)bp; + else + ifa->ifa_next = NULL; + } + return 0; + +err: + free(buf); + *ifahead = NULL; + errno = EINVAL; + return -1; +} + +int +unpriv_learnif(struct interface *ifp) +{ + struct srv_ctx *sctx = ifp->if_ctx->ctx_unpriv; + ssize_t result; + void *rdata; + size_t rdata_len, len; + int err; + struct interface *ifp0; + + err = srv_run(sctx, NULL, U_LEARNIF, &ifp->if_index, + sizeof(ifp->if_index), &result, &rdata, &rdata_len); + if (err == -1 || result == -1) + return -1; + + if (rdata_len != sizeof(*ifp)) { + errno = EINVAL; + return -1; + } + + ifp0 = (struct interface *)rdata; + len = strlcpy(ifp->if_name, ifp0->if_name, sizeof((ifp->if_name))); + if (len >= sizeof(ifp->if_name)) { + errno = EINVAL; + return -1; + } + + ifp->if_hwtype = ifp0->if_hwtype; + ifp->if_hwlen = ifp0->if_hwlen; + + if (ifp0->if_hwlen > sizeof(ifp->if_hwaddr)) { + errno = EINVAL; + return -1; + } + memcpy(ifp->if_hwaddr, ifp0->if_hwaddr, ifp0->if_hwlen); + + ifp->if_mtu = ifp0->if_mtu; + + return 0; +} + +int +unpriv_freeif(struct interface *ifp) +{ + struct srv_ctx *sctx = ifp->if_ctx->ctx_unpriv; + ssize_t result; + int err; + + err = srv_run(sctx, NULL, U_FREEIF, &ifp->if_index, + sizeof(ifp->if_index), &result, NULL, 0); + if (err == -1) + return -1; + return (int)result; +} + int unpriv_getaddrinfo(struct srv_ctx *ctx, const char *hostname, const char *servname, struct addrinfo *hints, @@ -154,8 +318,247 @@ unpriv_getaddrinfo(struct srv_ctx *ctx, const char *hostname, } static ssize_t -unpriv_dispatch(struct srv_ctx *sctx, struct plugin *p, unsigned int cmd, - const void *data, size_t len) +unpriv_dispatch_getifaddrs(struct srv_ctx *sctx, const void *data, size_t len) +{ + struct ifaddrs *ifaddrs = NULL, *ifa; + ssize_t err = -1; + void *buf = NULL; + uint8_t *bp, *sap; + socklen_t salen; + bool match_index, matching = false; + unsigned int if_index; + + if (len == sizeof(if_index)) { + memcpy(&if_index, data, sizeof(if_index)); + match_index = true; + } else + match_index = false; + + len = 0; + if (getifaddrs(&ifaddrs) == -1) + goto err; + if (ifaddrs == NULL) { + err = 0; + goto err; + } + + /* Work out the buffer length required. + * Ensure everything is aligned correctly, which does + * create a larger buffer than what is needed to send, + * but makes creating the same structure in the client + * much easier. */ + for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { + if (match_index) { + switch (if_link_match_index(ifa->ifa_addr, if_index)) { + case -1: + if (!matching) + continue; + break; + case 0: + matching = false; + continue; + case 1: + matching = true; + break; + } + } + len += ALIGN(sizeof(*ifa)); + len += ALIGN(IFNAMSIZ); + len += ALIGN(sizeof(salen) * IFA_NADDRS); + if (ifa->ifa_addr != NULL) + len += ALIGN(sa_len(ifa->ifa_addr)); + if (ifa->ifa_netmask != NULL) + len += ALIGN(sa_len(ifa->ifa_netmask)); + if (ifa->ifa_broadaddr != NULL) + len += ALIGN(sa_len(ifa->ifa_broadaddr)); +#ifdef BSD + /* + * On BSD we need to carry ifa_data so we can access + * if_data->ifi_link_state + */ + if (ifa->ifa_addr != NULL && + ifa->ifa_addr->sa_family == AF_LINK) + len += ALIGN(sizeof(struct if_data)); +#endif + } + + /* Use calloc to set everything to zero. + * This satisfies memory sanitizers because we don't write + * where we don't need to. */ + buf = calloc(1, len); + if (buf == NULL) { + freeifaddrs(ifaddrs); + return -1; + } + + bp = buf; + matching = false; + for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { + if (match_index) { + switch (if_link_match_index(ifa->ifa_addr, if_index)) { + case -1: + if (!matching) + continue; + break; + case 0: + matching = false; + continue; + case 1: + matching = true; + break; + } + } + if (if_sockaddr_active(sctx->srv_ctx, ifa->ifa_name, + ifa->ifa_addr) != 1) + continue; + + memcpy(bp, ifa, sizeof(*ifa)); + bp += ALIGN(sizeof(*ifa)); + + strlcpy((char *)bp, ifa->ifa_name, IFNAMSIZ); + bp += ALIGN(IFNAMSIZ); + sap = bp; + bp += ALIGN(sizeof(salen) * IFA_NADDRS); + +#define COPYINSA(addr) \ + do { \ + if ((addr) != NULL) \ + salen = sa_len((addr)); \ + else \ + salen = 0; \ + if (salen != 0) { \ + memcpy(sap, &salen, sizeof(salen)); \ + memcpy(bp, (addr), salen); \ + bp += ALIGN(salen); \ + } \ + sap += sizeof(salen); \ + } while (0 /*CONSTCOND */) + + COPYINSA(ifa->ifa_addr); + COPYINSA(ifa->ifa_netmask); + COPYINSA(ifa->ifa_broadaddr); + +#ifdef BSD + if (ifa->ifa_addr != NULL && + ifa->ifa_addr->sa_family == AF_LINK) { + salen = (socklen_t)sizeof(struct if_data); + memcpy(bp, ifa->ifa_data, salen); + bp += ALIGN(salen); + } else +#endif + salen = 0; + memcpy(sap, &salen, sizeof(salen)); + } + len = (size_t)(bp - (uint8_t *)buf); + + err = 0; +err: + freeifaddrs(ifaddrs); + return srv_send(sctx, NULL, U_GETIFADDRS, err, buf, len); +} + +static ssize_t +unpriv_dispatch_learnif(struct srv_ctx *sctx, const void *data, size_t len) +{ + struct ctx *ctx = sctx->srv_ctx; + struct interface *ifp = NULL; + unsigned int if_index; + ssize_t err = -1; + size_t nlen; + void *buf = NULL; + struct ifaddrs *ifaddrs = NULL, *ifa; + bool free_ifp = true; + + if (len != sizeof(if_index)) { + errno = EINVAL; + len = 0; + goto err; + } + memcpy(&if_index, data, len); + len = 0; + + TAILQ_FOREACH(ifp, ctx->ctx_ifaces, if_next) { + if (ifp->if_index == if_index) { + free_ifp = false; + break; + } + } + if (ifp == NULL) { + ifp = calloc(1, sizeof(*ifp)); + if (ifp == NULL) + goto err; + ifp->if_ctx = ctx; + ifp->if_index = if_index; + } + + if (getifaddrs(&ifaddrs) != 0) + goto err; + for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { + if (sa_is_link(ifa->ifa_addr) == 1 && + if_link_match_index(ifa->ifa_addr, if_index) == 1 && + if_sockaddr_active(sctx->srv_ctx, ifa->ifa_name, + ifa->ifa_addr) == 1) + break; + } + + if (ifa == NULL) { + errno = ESRCH; + goto err; + } + nlen = strlcpy(ifp->if_name, ifa->ifa_name, sizeof(ifp->if_name)); + if (nlen > sizeof(ifp->if_name)) { + errno = EINVAL; + goto err; + } + if_update(ifp, ifa->ifa_addr); + if_update_mtu(ifp); + + err = 0; + buf = ifp; + len = sizeof(*ifp); + TAILQ_INSERT_TAIL(ctx->ctx_ifaces, ifp, if_next); + free_ifp = false; + +err: + freeifaddrs(ifaddrs); + if (free_ifp) + if_free(ifp); + return srv_send(sctx, NULL, U_LEARNIF, err, buf, len); +} + +static ssize_t +unpriv_dispatch_freeif(struct srv_ctx *sctx, const void *data, size_t len) +{ + struct ctx *ctx = sctx->srv_ctx; + unsigned int if_index; + struct interface *ifp; + ssize_t err = -1; + + if (len != sizeof(if_index)) { + errno = EINVAL; + goto err; + } + memcpy(&if_index, data, sizeof(if_index)); + + TAILQ_FOREACH(ifp, ctx->ctx_ifaces, if_next) { + if (ifp->if_index == if_index) + break; + } + if (ifp == NULL) { + errno = ESRCH; + goto err; + } + + TAILQ_REMOVE(ctx->ctx_ifaces, ifp, if_next); + if_free(ifp); + err = 0; + +err: + return srv_send(sctx, NULL, U_FREEIF, err, NULL, 0); +} + +static ssize_t +unpriv_dispatch_getaddrinfo(struct srv_ctx *sctx, const void *data, size_t len) { const struct unpriv_getaddrinfo *u_gai = data; const char *hostname, *servname; @@ -166,15 +569,7 @@ unpriv_dispatch(struct srv_ctx *sctx, struct plugin *p, unsigned int cmd, size_t n; ssize_t res; - if (p != NULL) { - if (p->p_dispatch != NULL) - return p->p_dispatch(p, sctx, cmd, data, len); - errno = ENOSYS; - logerr(__func__); - goto err; - } - - if (cmd != U_GETADDRINFO || len != sizeof(*u_gai)) { + if (len != sizeof(*u_gai)) { errno = EINVAL; logerr(__func__); goto err; @@ -234,7 +629,35 @@ unpriv_dispatch(struct srv_ctx *sctx, struct plugin *p, unsigned int cmd, err: freeaddrinfo(ai_result); free(reply); - return srv_send(sctx, NULL, cmd, err, NULL, 0); + return srv_send(sctx, NULL, U_GETADDRINFO, err, NULL, 0); +} + +static ssize_t +unpriv_dispatch(struct srv_ctx *sctx, struct plugin *p, unsigned int cmd, + const void *data, size_t len) +{ + if (p != NULL) { + if (p->p_dispatch != NULL) + return p->p_dispatch(p, sctx, cmd, data, len); + errno = ENOSYS; + logerr(__func__); + return srv_send(sctx, NULL, cmd, -1, NULL, 0); + } + + switch (cmd) { + case U_GETIFADDRS: + return unpriv_dispatch_getifaddrs(sctx, data, len); + case U_LEARNIF: + return unpriv_dispatch_learnif(sctx, data, len); + case U_FREEIF: + return unpriv_dispatch_freeif(sctx, data, len); + case U_GETADDRINFO: + return unpriv_dispatch_getaddrinfo(sctx, data, len); + default: + errno = EINVAL; + logerr(__func__); + return srv_send(sctx, NULL, cmd, -1, NULL, 0); + } } struct srv_ctx * diff --git a/src/unpriv.h b/src/unpriv.h index 121116d..3e2d7fd 100644 --- a/src/unpriv.h +++ b/src/unpriv.h @@ -29,10 +29,17 @@ #ifndef UNPRIV_H #define UNPRIV_H +struct ctx; struct srv_ctx; struct addrinfo; +struct ifaddrs; +struct interface; struct srv_ctx *unpriv_init(struct ctx *); +int unpriv_getifaddrs(struct srv_ctx *ctx, struct ifaddrs **ifahead, + const unsigned int *match_if_index); +int unpriv_learnif(struct interface *ifp); +int unpriv_freeif(struct interface *ifp); int unpriv_getaddrinfo(struct srv_ctx *, const char *, const char *, struct addrinfo *, struct addrinfo **restrict); void unpriv_freeaddrinfo(struct addrinfo *); From 7d2e66905e142bc7ac1fb5f6eb14d5dfd1bec779 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Thu, 16 Apr 2026 13:49:34 +0100 Subject: [PATCH 02/19] Address sensible review comments. --- src/bpf.c | 2 +- src/if.c | 11 ++++++----- src/plugins/auto.c | 2 +- src/route.c | 11 +++++++---- src/service.c | 13 +++++++++---- src/unpriv.c | 13 ++++++++----- 6 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/bpf.c b/src/bpf.c index 24798d3..1fd922d 100644 --- a/src/bpf.c +++ b/src/bpf.c @@ -527,7 +527,7 @@ bpf_open(const struct interface *ifp, int (*filter)(const struct bpf *), } /* Keep support for reading around, we might need it in the future. */ -#if 1 +#if 0 /* BPF requires that we read the entire buffer. * So we pass the buffer in the API so we can loop on >1 packet. */ ssize_t diff --git a/src/if.c b/src/if.c index 1ff8da5..a1f4957 100644 --- a/src/if.c +++ b/src/if.c @@ -166,9 +166,8 @@ if_sockaddr_active(struct ctx *ctx, const char *if_name, const struct sockaddr_dl *sdl = (const void *)sa; struct if_laddrreq iflr = { .flags = IFLR_PREFIX }; - strlcpy(iflr.iflr_name, ifa->ifa_name, sizeof(iflr.iflr_name)); - memcpy(&iflr.addr, ifa->ifa_addr, - MIN(ifa->ifa_addr->sa_len, sizeof(iflr.addr))); + strlcpy(iflr.iflr_name, if_name, sizeof(iflr.iflr_name)); + memcpy(&iflr.addr, sa, MIN(sa->sa_len, sizeof(iflr.addr))); iflr.flags = IFLR_PREFIX; iflr.prefixlen = (unsigned int)sdl->sdl_alen * NBBY; if (ioctl(ctx->ctx_pf_link_fd, SIOCGLIFADDR, &iflr) == -1 || @@ -195,8 +194,10 @@ if_link_match_index(const struct sockaddr *sa, unsigned int if_index) const struct sockaddr_dl *sdl = (const void *)sa; return sdl->sdl_index == if_index ? 1 : 0; #elif defined(AF_PACKET) - const struct sockaddr_ll *sll = (const void *)ifa->ifa_addr; + const struct sockaddr_ll *sll = (const void *)sa; return (unsigned int)sll->sll_index == if_index ? 1 : 0; +#else +#error undefined platform #endif } @@ -346,7 +347,7 @@ if_findifpfromcmsg(struct ctx *ctx, struct msghdr *msg, void *to) } } if (ifp->if_flags & IF_ACTIVE) { - log_infox("%s: activated interface (%d)", ifp->if_name, + loginfox("%s: activated interface (%d)", ifp->if_name, ifp->if_index); if (dhcpsd_configure_pools(ifp) == -1) { logerr("%s: dhcpsd_configure_pools", diff --git a/src/plugins/auto.c b/src/plugins/auto.c index 4991288..47a6459 100644 --- a/src/plugins/auto.c +++ b/src/plugins/auto.c @@ -119,7 +119,7 @@ auto_configure_pools(__unused struct plugin *p, struct interface *ifp) sizeof(*pool) * (ifp->if_npools + 1)); if (pool == NULL) { logerr("%s: realloc", __func__); - free(ifaddrs); + freeifaddrs(ifaddrs); return -1; } ifp->if_pools = pool; diff --git a/src/route.c b/src/route.c index ffbcccd..94c2963 100644 --- a/src/route.c +++ b/src/route.c @@ -133,7 +133,10 @@ link_open(struct ctx *ctx) if (lctx == NULL) return -1; + /* lctx will be freed at exit regardless if any error below + * so there is no need to free it on an error path. */ lctx->link_ctx = ctx; + ctx->ctx_link = lctx; lctx->link_fd = xsocket(PF_ROUTE, SOCK_RAW | SOCK_CXNB, AF_UNSPEC); if (lctx->link_fd == -1) @@ -141,13 +144,13 @@ link_open(struct ctx *ctx) #ifdef SO_RERROR n = 1; - if (setsockopt(ctx->link_fd, SOL_SOCKET, SO_RERROR, &n, sizeof(n)) == + if (setsockopt(lctx->link_fd, SOL_SOCKET, SO_RERROR, &n, sizeof(n)) == -1) logerr("%s: SO_RERROR", __func__); #endif #if defined(RO_MSGFILTER) - if (setsockopt(ctx->link_fd, PF_ROUTE, RO_MSGFILTER, &msgfilter, + if (setsockopt(lctx->link_fd, PF_ROUTE, RO_MSGFILTER, &msgfilter, sizeof(msgfilter)) == -1) logerr(__func__); #elif defined(ROUTE_MSGFILTER) @@ -155,8 +158,8 @@ link_open(struct ctx *ctx) msgfilter_mask = 0; for (i = 0; i < __arraycount(msgfilter); i++) msgfilter_mask |= ROUTE_FILTER(msgfilter[i]); - if (setsockopt(ctx->link_fd, PF_ROUTE, ROUTE_MSGFILTER, &msgfilter_mask, - sizeof(msgfilter_mask)) == -1) + if (setsockopt(lctx->link_fd, PF_ROUTE, ROUTE_MSGFILTER, + &msgfilter_mask, sizeof(msgfilter_mask)) == -1) logerr(__func__); #else #warning kernel does not support route message filtering diff --git a/src/service.c b/src/service.c index d1588a3..4802c9f 100644 --- a/src/service.c +++ b/src/service.c @@ -54,6 +54,7 @@ struct srv_cmd { static ssize_t srv_recv(struct srv_ctx *sctx, unsigned short e) { + unsigned int options = sctx->srv_ctx->ctx_options; struct srv_result *sr = &sctx->srv_result; struct srv_cmd cmd; struct iovec iov[] = { @@ -67,12 +68,13 @@ srv_recv(struct srv_ctx *sctx, unsigned short e) if (e & ELE_HANGUP) { hangup: - if (sctx->srv_ctx->ctx_options & DHCPSD_MAIN) + if (options & DHCPSD_MAIN) logdebugx("%s(%d): fd %d hungup", __func__, getpid(), sctx->srv_fd); close(sctx->srv_fd); sctx->srv_fd = -1; - eloop_exit(sctx->srv_ctx->ctx_eloop, EXIT_SUCCESS); + eloop_exit(sctx->srv_ctx->ctx_eloop, + options & DHCPSD_EXITING ? EXIT_SUCCESS : EXIT_FAILURE); return -1; } if (e != ELE_READ) { @@ -137,8 +139,11 @@ srv_recvl(void *arg, unsigned short e) { struct srv_ctx *sctx = arg; - if (srv_recv(sctx, e) == -1 && !(e & ELE_HANGUP)) - eloop_exit(sctx->srv_ctx->ctx_eloop, EXIT_FAILURE); + if (srv_recv(sctx, e) == -1 && !(e & ELE_HANGUP)) { + unsigned int options = sctx->srv_ctx->ctx_options; + eloop_exit(sctx->srv_ctx->ctx_eloop, + options & DHCPSD_EXITING ? EXIT_SUCCESS : EXIT_FAILURE); + } } ssize_t diff --git a/src/unpriv.c b/src/unpriv.c index 4b9b632..a4b9ca8 100644 --- a/src/unpriv.c +++ b/src/unpriv.c @@ -467,7 +467,7 @@ unpriv_dispatch_learnif(struct srv_ctx *sctx, const void *data, size_t len) size_t nlen; void *buf = NULL; struct ifaddrs *ifaddrs = NULL, *ifa; - bool free_ifp = true; + bool found_if_index = false; if (len != sizeof(if_index)) { errno = EINVAL; @@ -479,7 +479,7 @@ unpriv_dispatch_learnif(struct srv_ctx *sctx, const void *data, size_t len) TAILQ_FOREACH(ifp, ctx->ctx_ifaces, if_next) { if (ifp->if_index == if_index) { - free_ifp = false; + found_if_index = true; break; } } @@ -516,12 +516,15 @@ unpriv_dispatch_learnif(struct srv_ctx *sctx, const void *data, size_t len) err = 0; buf = ifp; len = sizeof(*ifp); - TAILQ_INSERT_TAIL(ctx->ctx_ifaces, ifp, if_next); - free_ifp = false; + if (!found_if_index) { + TAILQ_INSERT_TAIL(ctx->ctx_ifaces, ifp, if_next); + /* stop ifp being freed below */ + found_if_index = true; + } err: freeifaddrs(ifaddrs); - if (free_ifp) + if (!found_if_index) if_free(ifp); return srv_send(sctx, NULL, U_LEARNIF, err, buf, len); } From a52125c774d2cfd28f2e7632130c5573ed37ceb3 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Thu, 16 Apr 2026 13:53:05 +0100 Subject: [PATCH 03/19] Fix a compile error on Linux --- src/if.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/if.c b/src/if.c index a1f4957..65555c7 100644 --- a/src/if.c +++ b/src/if.c @@ -195,7 +195,7 @@ if_link_match_index(const struct sockaddr *sa, unsigned int if_index) return sdl->sdl_index == if_index ? 1 : 0; #elif defined(AF_PACKET) const struct sockaddr_ll *sll = (const void *)sa; - return (unsigned int)sll->sll_index == if_index ? 1 : 0; + return (unsigned int)sll->sll_ifindex == if_index ? 1 : 0; #else #error undefined platform #endif From ef68a9b6c67c4b98e3997706415c2b741cf36310 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Thu, 16 Apr 2026 14:28:56 +0100 Subject: [PATCH 04/19] lua: output configured pools in the main process as the unpriv process only outputs in debug mode --- src/plugins/lua.c | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/plugins/lua.c b/src/plugins/lua.c index f3a7479..3ecbf5c 100644 --- a/src/plugins/lua.c +++ b/src/plugins/lua.c @@ -95,7 +95,6 @@ lua_run_configure_pools(struct plugin *p, struct srv_ctx *sctx, ssize_t err = -1; struct dhcp_pool *pool, *pools = NULL; int laddress, lnetmask, lfrom, lto, llease_time; - uint8_t cidr; const char *saddress, *snetmask, *sfrom, *sto; in_addr_t address, netmask, from, to; uint32_t lease_time; @@ -202,16 +201,8 @@ lua_run_configure_pools(struct plugin *p, struct srv_ctx *sctx, pool->dp_mask.s_addr = netmask; pool->dp_from.s_addr = from; pool->dp_to.s_addr = to; - cidr = inet_ntocidr(&pool->dp_mask); pool->dp_lease_time = lease_time; - if (lease_time != 0) - loginfox("%s: %s: pool for %s/%d: %s - %s (%u secs)", - ifname, lua_name, saddress, cidr, sfrom, sto, - lease_time); - else - loginfox("%s: %s: pool for %s/%d: %s - %s", ifname, - lua_name, saddress, cidr, sfrom, sto); skip: lua_pop(L, 4); @@ -322,8 +313,9 @@ lua_run_lookup_addr(struct plugin *p, struct srv_ctx *sctx, const void *data, /* Aligns bootp */ memmove(UNCONST(data), datap, len); - if (len < sizeof(*l->l_req)) { - /* Allow for a truncated vendor area if DHCP */ + /* Allow for a truncated vendor area if it can at least + * contain a DHCP cookie. */ + if (len < offsetof(struct bootp, vend) + sizeof(uint32_t)) { if (len < offsetof(struct bootp, vend) + sizeof(uint32_t) || dhcp_cookiecmp(l->l_req) != 0) { errno = EINVAL; @@ -966,7 +958,9 @@ lua_configure_pools(struct plugin *p, struct interface *ifp) ssize_t err, result; void *_pools; size_t poolslen, npools; - struct dhcp_pool *pool; + struct dhcp_pool *pool, *epool; + uint32_t cidr; + char addr[INET_ADDRSTRLEN], from[INET_ADDRSTRLEN], to[INET_ADDRSTRLEN]; err = srv_run(p->p_ctx->ctx_unpriv, p, L_CONFIGUREPOOLS, ifp->if_name, strlen(ifp->if_name) + 1, &result, &_pools, &poolslen); @@ -984,6 +978,22 @@ lua_configure_pools(struct plugin *p, struct interface *ifp) pool += ifp->if_npools; memcpy(pool, _pools, poolslen); ifp->if_npools += npools; + + for (pool = _pools, epool = pool + npools; pool != epool; pool++) { + inet_ntop(AF_INET, &pool->dp_addr, addr, sizeof(addr)); + cidr = inet_ntocidr(&pool->dp_mask); + inet_ntop(AF_INET, &pool->dp_from, from, sizeof(from)); + inet_ntop(AF_INET, &pool->dp_to, to, sizeof(to)); + + if (pool->dp_lease_time != 0) + loginfox("%s: %s: pool for %s/%d: %s - %s (%u secs)", + ifp->if_name, lua_name, addr, cidr, from, to, + pool->dp_lease_time); + else + loginfox("%s: %s: pool for %s/%d: %s - %s", + ifp->if_name, lua_name, addr, cidr, from, to); + } + return (ssize_t)npools; } From fd331910faf4c0bcfe8607978a12225186598d60 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Thu, 16 Apr 2026 14:36:07 +0100 Subject: [PATCH 05/19] define ALIGN and ALIGNBYTES if not defined already --- src/common.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/common.h b/src/common.h index fd075c0..7fd528d 100644 --- a/src/common.h +++ b/src/common.h @@ -49,6 +49,13 @@ #define UNUSED(a) (void)(a) #define ARRAYCOUNT(a) (sizeof((a)) / sizeof((a)[0])) +#ifndef ALIGNBYTES +#define ALIGNBYTES (sizeof(int) - 1) +#endif +#ifndef ALIGN +#define ALIGN(p) (((unsigned int)(p) + ALIGNBYTES) & ~ALIGNBYTES) +#endif + /* Some systems don't define timespec macros */ #ifndef timespecclear #define timespecclear(tsp) (tsp)->tv_sec = (time_t)((tsp)->tv_nsec = 0L) From 556721cd71b2a1c23aefce9ff30df80cafb1ce93 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Thu, 16 Apr 2026 14:40:37 +0100 Subject: [PATCH 06/19] Detect route(4) better --- configure | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/configure b/configure index e4868b2..e52b781 100755 --- a/configure +++ b/configure @@ -592,13 +592,13 @@ rm -rf _inet_ntoa.* _inet_ntoa $abort && exit 1 if [ -z "$LINK_SRC" ]; then - printf "Testing for route ..." + printf "Testing for route ... " cat << EOF >_route.c #include int main(void) { -#ifdef RTM_IFINFO + struct rt_msghdr rtm = { .rtm_type = 0 }; + rtm.rtm_type = RTM_IFINFO; return 0; -#endif } EOF if $XCC _route.c -o _route 2>&3; then From 3e8db424ba076d6daf0a13ab9859e81476c42b0b Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Thu, 16 Apr 2026 14:54:57 +0100 Subject: [PATCH 07/19] Address yet more review comments --- src/if.c | 6 +++--- src/plugins/auto.c | 4 ++-- src/plugins/lua.c | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/if.c b/src/if.c index 65555c7..07a6b4e 100644 --- a/src/if.c +++ b/src/if.c @@ -135,9 +135,9 @@ if_update(struct interface *ifp, struct sockaddr *sa) struct sockaddr_ll *sll = (void *)sa; ifp->if_index = (unsigned int)sll->sll_ifindex; ifp->if_hwtype = sll->sll_hatype; - if (ifp->if_hwlen <= sizeof(ifp->if_hwaddr)) { + if (sll->sll_halen <= sizeof(ifp->if_hwaddr)) { ifp->if_hwlen = sll->sll_halen; - memcpy(ifp->if_hwaddr, sll->sll_addr, ifp->if_hwlen); + memcpy(ifp->if_hwaddr, sll->sll_addr, sll->sll_halen); } #endif } @@ -238,7 +238,7 @@ if_learnifaces(struct ctx *ctx) err = 0; err: - freeifaddrs(ifaddrs); + free(ifaddrs); return err; } diff --git a/src/plugins/auto.c b/src/plugins/auto.c index 47a6459..074d059 100644 --- a/src/plugins/auto.c +++ b/src/plugins/auto.c @@ -119,7 +119,7 @@ auto_configure_pools(__unused struct plugin *p, struct interface *ifp) sizeof(*pool) * (ifp->if_npools + 1)); if (pool == NULL) { logerr("%s: realloc", __func__); - freeifaddrs(ifaddrs); + free(ifaddrs); return -1; } ifp->if_pools = pool; @@ -153,7 +153,7 @@ auto_configure_pools(__unused struct plugin *p, struct interface *ifp) npools++; } - freeifaddrs(ifaddrs); + free(ifaddrs); return npools; } diff --git a/src/plugins/lua.c b/src/plugins/lua.c index 3ecbf5c..c246cd5 100644 --- a/src/plugins/lua.c +++ b/src/plugins/lua.c @@ -204,7 +204,7 @@ lua_run_configure_pools(struct plugin *p, struct srv_ctx *sctx, pool->dp_lease_time = lease_time; skip: - lua_pop(L, 4); + lua_pop(L, llease_time == LUA_TNUMBER ? 5 : 4); lua_gettable(L, -1); lua_pop(L, 1); From 235c90f9356ebca2def7bd3f88c0dd33dd825446 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Thu, 16 Apr 2026 20:04:40 +0100 Subject: [PATCH 08/19] Add netlink support --- configure | 22 ++++- src/common.c | 8 ++ src/if.c | 2 - src/netlink.c | 240 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/seccomp.c | 9 +- src/unpriv.c | 4 +- 6 files changed, 278 insertions(+), 7 deletions(-) create mode 100644 src/netlink.c diff --git a/configure b/configure index e52b781..1e7d28e 100755 --- a/configure +++ b/configure @@ -311,7 +311,7 @@ EOF esac if [ "$SANITIZEADDRESS" = yes ]; then - printf "Testing compiler supports address sanitisation ..." + printf "Testing compiler supports address sanitisation ... " cat <_test.c int main(void) { return 0; @@ -609,6 +609,26 @@ EOF fi rm -rf _route.* _route fi + +if [ -z "$LINK_SRC" ]; then + printf "Testing for netlink ... " + cat << EOF >_netlink.c +#include +#include +#include +#include +int main(void) { + return 0; +} +EOF + if $XCC _netlink.c -o _netlink 2>&3; then + LINK_SRC="netlink.c" + echo "yes" + else + echo "no" + fi + rm -rf _netlink.* _netlink +fi if [ -z "$LINK_SRC" ]; then echo "no mechanism for detecting interface departures found" >&2 exit 1 diff --git a/src/common.c b/src/common.c index d7a6f45..e23557c 100644 --- a/src/common.c +++ b/src/common.c @@ -30,6 +30,10 @@ #include +#ifdef AF_PACKET +#include +#endif + #include #include #include @@ -434,6 +438,10 @@ sa_len(const struct sockaddr *sa) return sa->sa_len; #else switch (sa->sa_family) { +#ifdef AF_PACKET + case AF_PACKET: + return sizeof(struct sockaddr_ll); +#endif case AF_INET: return sizeof(struct sockaddr_in); case AF_INET6: diff --git a/src/if.c b/src/if.c index 07a6b4e..ac9280b 100644 --- a/src/if.c +++ b/src/if.c @@ -213,8 +213,6 @@ if_learnifaces(struct ctx *ctx) return -1; } for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) { - if (ifa->ifa_addr == NULL) - continue; if (!sa_is_link(ifa->ifa_addr)) continue; diff --git a/src/netlink.c b/src/netlink.c new file mode 100644 index 0000000..aa6ba20 --- /dev/null +++ b/src/netlink.c @@ -0,0 +1,240 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpsd - netlink(7) supprt + * Copyright (c) 2025 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "dhcpsd.h" +#include "eloop.h" +#include "if.h" +#include "logerr.h" +#include "queue.h" + +struct link_ctx { + struct ctx *link_ctx; + int link_fd; +}; + +static int +netlink_get(struct link_ctx *lctx, struct iovec *iov, int fd, int flags, + int (*cb)(struct link_ctx *, struct nlmsghdr *)) +{ + struct sockaddr_nl nladdr = { .nl_pid = 0 }; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = iov, + .msg_iovlen = 1, + }; + size_t len; + struct nlmsghdr *nlm; + int r = 0; + unsigned int again; + bool terminated; + +recv_again: + len = (size_t)recvmsg(fd, &msg, flags); + if (len == 0 || (ssize_t)len == -1) + return (int)len; + + /* Check sender */ + if (msg.msg_namelen != sizeof(nladdr)) { + errno = EINVAL; + return -1; + } + + /* Ignore message if it is not from kernel */ + if (nladdr.nl_pid != 0) + return 0; + + again = 0; + terminated = false; + for (nlm = iov->iov_base; nlm && NLMSG_OK(nlm, len); + nlm = NLMSG_NEXT(nlm, len)) { + again = (nlm->nlmsg_flags & NLM_F_MULTI); + if (nlm->nlmsg_type == NLMSG_NOOP) + continue; + + if (nlm->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err; + + if (nlm->nlmsg_len - sizeof(*nlm) < sizeof(*err)) { + errno = EBADMSG; + return -1; + } + err = (struct nlmsgerr *)NLMSG_DATA(nlm); + if (err->error != 0) { + errno = -err->error; + return -1; + } + again = 0; + terminated = true; + break; + } + if (nlm->nlmsg_type == NLMSG_DONE) { + again = 0; + terminated = true; + break; + } + if (cb == NULL) + continue; + r = cb(lctx, nlm); + } + + if ((again || !terminated) && (lctx != NULL && lctx->link_fd != fd)) + goto recv_again; + + return r; +} + +static int +netlink_link(struct link_ctx *lctx, struct nlmsghdr *nlm) +{ + struct ctx *ctx = lctx->link_ctx; + size_t len; + struct ifinfomsg *ifi; + struct interface *ifp; + + len = nlm->nlmsg_len - sizeof(*nlm); + if ((size_t)len < sizeof(*ifi)) { + errno = EBADMSG; + return -1; + } + + ifi = NLMSG_DATA(nlm); + if (nlm->nlmsg_type != RTM_DELLINK && ifi->ifi_flags & IFF_UP) + return 0; + + TAILQ_FOREACH(ifp, ctx->ctx_ifaces, if_next) { + if (ifp->if_index == (unsigned int)ifi->ifi_index) + break; + } + if (ifp == NULL) + return 0; + + if (nlm->nlmsg_type == RTM_DELLINK) + loginfox("%s: interface departed", ifp->if_name); + else if (!(ifi->ifi_flags & IFF_UP)) + loginfox("%s: interface is down", ifp->if_name); + else + logerrx("%s: interface going away for no reason", ifp->if_name); + TAILQ_REMOVE(ctx->ctx_ifaces, ifp, if_next); + if_free(ifp); + return 0; +} + +static int +netlink_dispatch(struct link_ctx *lctx, struct nlmsghdr *nlm) +{ + switch (nlm->nlmsg_type) { + case RTM_DELLINK: + case RTM_NEWLINK: + return netlink_link(lctx, nlm); + } + return 0; +} + +static void +netlink_handle(void *arg, unsigned short e) +{ + struct link_ctx *lctx = arg; + unsigned char buf[16 * 1024]; + struct iovec iov = { + .iov_base = buf, + .iov_len = sizeof(buf), + }; + + UNUSED(e); + + if (netlink_get(lctx, &iov, lctx->link_fd, MSG_DONTWAIT, + netlink_dispatch) == -1) + logerr("%s: netlink_get", __func__); +} + +int +link_open(struct ctx *ctx) +{ + struct link_ctx *lctx = malloc(sizeof(*lctx)); + struct sockaddr_nl nl = { .nl_family = AF_NETLINK, + .nl_groups = RTMGRP_LINK }; + int on = 1; + + if (lctx == NULL) + return -1; + + /* lctx will be freed at exit regardless if any error below + * so there is no need to free it on an error path. */ + lctx->link_ctx = ctx; + ctx->ctx_link = lctx; + + lctx->link_fd = xsocket(AF_NETLINK, SOCK_RAW | SOCK_CXNB, + NETLINK_ROUTE); + if (lctx->link_fd == -1) + return -1; + + if (bind(lctx->link_fd, (struct sockaddr *)&nl, sizeof(nl)) == -1) { + logerr("%s: bind", __func__); + return -1; + } + + if (setsockopt(lctx->link_fd, SOL_NETLINK, NETLINK_BROADCAST_ERROR, &on, + sizeof(on)) == -1) + logerr("%s: NETLINK_BROADCAST_ERROR", __func__); + + if (eloop_event_add(ctx->ctx_eloop, lctx->link_fd, ELE_READ, + netlink_handle, lctx) == -1) { + logerr("%s: eloop_event_add", __func__); + return -1; + } + + return 0; +} + +void +link_free(struct ctx *ctx) +{ + struct link_ctx *lctx = ctx->ctx_link; + + if (lctx == NULL) + return; + if (lctx->link_fd != -1) + close(lctx->link_fd); + free(lctx); + ctx->ctx_link = NULL; +} diff --git a/src/seccomp.c b/src/seccomp.c index d65226a..bd8cceb 100644 --- a/src/seccomp.c +++ b/src/seccomp.c @@ -398,12 +398,15 @@ static struct sock_filter seccomp_filter[] = { SECCOMP_ALLOW(__NR_rt_sigtimedwait), #endif #ifdef VALGRIND +/* This is dangerous, and also pointless as in privsep + * we are no longer root and thus cannot unlink the valgrind + * pipes anyway. */ #ifdef __NR_unlink - /* This is dangerous, and also pointless as in privsep - * we are no longer root and thus cannot unlink the valgrind - * pipes anyway. */ SECCOMP_ALLOW(__NR_unlink), #endif +#ifdef __NR_unlinkat + SECCOMP_ALLOW(__NR_unlinkat), +#endif #endif /* hardened-malloc */ diff --git a/src/unpriv.c b/src/unpriv.c index a4b9ca8..81f9d8f 100644 --- a/src/unpriv.c +++ b/src/unpriv.c @@ -454,7 +454,9 @@ unpriv_dispatch_getifaddrs(struct srv_ctx *sctx, const void *data, size_t len) err = 0; err: freeifaddrs(ifaddrs); - return srv_send(sctx, NULL, U_GETIFADDRS, err, buf, len); + err = srv_send(sctx, NULL, U_GETIFADDRS, err, buf, len); + free(buf); + return err; } static ssize_t From acaf53da65fc6df7ddbf55d82e0aa8aedf278a34 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Thu, 16 Apr 2026 21:03:42 +0100 Subject: [PATCH 09/19] Fix compile on NetBSD. --- configure | 5 ++--- src/plugins/lua.c | 14 ++++++-------- src/unpriv.c | 1 + 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/configure b/configure index 1e7d28e..0bee3c8 100755 --- a/configure +++ b/configure @@ -596,9 +596,8 @@ if [ -z "$LINK_SRC" ]; then cat << EOF >_route.c #include int main(void) { - struct rt_msghdr rtm = { .rtm_type = 0 }; - rtm.rtm_type = RTM_IFINFO; - return 0; + struct rt_msghdr rtm = { .rtm_type = RTM_IFINFO }; + return (int)rtm.rtm_type; } EOF if $XCC _route.c -o _route 2>&3; then diff --git a/src/plugins/lua.c b/src/plugins/lua.c index c246cd5..9276615 100644 --- a/src/plugins/lua.c +++ b/src/plugins/lua.c @@ -146,6 +146,7 @@ lua_run_configure_pools(struct plugin *p, struct srv_ctx *sctx, lnetmask = lua_getfield(L, -2, "netmask"); lfrom = lua_getfield(L, -3, "from"); lto = lua_getfield(L, -4, "to"); + llease_time = lua_getfield(L, -5, "lease_time"); if (laddress != LUA_TSTRING) { logerrx("%s: no address in pool", ifname); goto skip; @@ -162,10 +163,10 @@ lua_run_configure_pools(struct plugin *p, struct srv_ctx *sctx, logerrx("%s: no too in pool", ifname); goto skip; } - saddress = lua_tostring(L, -4); - snetmask = lua_tostring(L, -3); - sfrom = lua_tostring(L, -2); - sto = lua_tostring(L, -1); + saddress = lua_tostring(L, -5); + snetmask = lua_tostring(L, -4); + sfrom = lua_tostring(L, -3); + sto = lua_tostring(L, -2); if (inet_pton(AF_INET, saddress, &address) != 1) { logerrx("%s: not an ip address %s", lua_name, saddress); goto skip; @@ -182,8 +183,6 @@ lua_run_configure_pools(struct plugin *p, struct srv_ctx *sctx, logerrx("%s: not an ip address %s", lua_name, sto); goto skip; } - - llease_time = lua_getfield(L, -5, "lease_time"); if (llease_time == LUA_TNUMBER) lease_time = (uint32_t)lua_tonumber(L, -1); else @@ -204,8 +203,7 @@ lua_run_configure_pools(struct plugin *p, struct srv_ctx *sctx, pool->dp_lease_time = lease_time; skip: - lua_pop(L, llease_time == LUA_TNUMBER ? 5 : 4); - + lua_pop(L, 5); lua_gettable(L, -1); lua_pop(L, 1); if (!array) diff --git a/src/unpriv.c b/src/unpriv.c index 81f9d8f..6d422ee 100644 --- a/src/unpriv.c +++ b/src/unpriv.c @@ -37,6 +37,7 @@ #ifdef AF_LINK #include #include +#include #include #undef AF_PACKET /* Newer Illumos defines this */ #endif From a90087896eda575c0f34bdac0d07074ad135dd42 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Thu, 16 Apr 2026 21:56:15 +0100 Subject: [PATCH 10/19] Actually work on NetBSD --- src/dhcp.c | 4 +++- src/dhcpsd.c | 5 +++-- src/if.c | 18 +++++++++++------- src/netlink.c | 2 +- src/route.c | 36 +++++++++++++++++++++++++++++++++++- 5 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/dhcp.c b/src/dhcp.c index 8585613..149088c 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -1303,10 +1303,12 @@ dhcp_handlebootp(struct interface *ifp, struct bootp *bootp, size_t len, wanted = dhcp_lease_findaddr(ctx, &paddr); if (wanted != NULL && !dhcp_lease_avail(lease, wanted, &now)) { logwarnx( - "%s: plugin assigned address unavailable: 0x%x %s", + "%s: plugin assigned address in-use: 0x%x %s", ifp->if_name, bootp->xid, inet_ntoa(paddr)); +#if 0 paddr.s_addr = INADDR_ANY; wanted = NULL; +#endif } break; } diff --git a/src/dhcpsd.c b/src/dhcpsd.c index 125cfcf..0b757d7 100644 --- a/src/dhcpsd.c +++ b/src/dhcpsd.c @@ -470,8 +470,6 @@ main(int argc, char **argv) logerr("%s: PF_INET", __func__); goto exit; } - if (ctx.ctx_options & DHCPSD_RUN) - goto run; #ifdef IFLR_ACTIVE ctx.ctx_pf_link_fd = xsocket(PF_LINK, SOCK_DGRAM | SOCK_CLOEXEC, 0); @@ -481,6 +479,9 @@ main(int argc, char **argv) } #endif + if (ctx.ctx_options & DHCPSD_RUN) + goto run; + if (link_open(&ctx) == -1) { logerr("%s: link_open", __func__); goto exit; diff --git a/src/if.c b/src/if.c index ac9280b..3688bec 100644 --- a/src/if.c +++ b/src/if.c @@ -164,14 +164,17 @@ if_sockaddr_active(struct ctx *ctx, const char *if_name, { #ifdef IFLR_ACTIVE const struct sockaddr_dl *sdl = (const void *)sa; - struct if_laddrreq iflr = { .flags = IFLR_PREFIX }; + struct if_laddrreq iflr = { + .flags = IFLR_PREFIX, + .prefixlen = (unsigned int)sdl->sdl_alen * NBBY, + }; strlcpy(iflr.iflr_name, if_name, sizeof(iflr.iflr_name)); memcpy(&iflr.addr, sa, MIN(sa->sa_len, sizeof(iflr.addr))); - iflr.flags = IFLR_PREFIX; - iflr.prefixlen = (unsigned int)sdl->sdl_alen * NBBY; - if (ioctl(ctx->ctx_pf_link_fd, SIOCGLIFADDR, &iflr) == -1 || - !(iflr.flags & IFLR_ACTIVE)) + + if (ioctl(ctx->ctx_pf_link_fd, SIOCGLIFADDR, &iflr) == -1) + return 0; + if (!(iflr.flags & IFLR_ACTIVE)) return 0; #else UNUSED(ctx); @@ -330,7 +333,8 @@ if_findifpfromcmsg(struct ctx *ctx, struct msghdr *msg, void *to) if (unpriv_learnif(ifp) == -1) { logerr("%s: unpriv_learnif", __func__); - if_free(ifp); + /* Avoid neededless checks */ + free(ifp); return NULL; } @@ -345,7 +349,7 @@ if_findifpfromcmsg(struct ctx *ctx, struct msghdr *msg, void *to) } } if (ifp->if_flags & IF_ACTIVE) { - loginfox("%s: activated interface (%d)", ifp->if_name, + logdebugx("%s: activated interface (%d)", ifp->if_name, ifp->if_index); if (dhcpsd_configure_pools(ifp) == -1) { logerr("%s: dhcpsd_configure_pools", diff --git a/src/netlink.c b/src/netlink.c index aa6ba20..a6b8e8b 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -149,7 +149,7 @@ netlink_link(struct link_ctx *lctx, struct nlmsghdr *nlm) return 0; if (nlm->nlmsg_type == RTM_DELLINK) - loginfox("%s: interface departed", ifp->if_name); + loginfox("%s: interface has departed", ifp->if_name); else if (!(ifi->ifi_flags & IFF_UP)) loginfox("%s: interface is down", ifp->if_name); else diff --git a/src/route.c b/src/route.c index 94c2963..0cc019d 100644 --- a/src/route.c +++ b/src/route.c @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -77,6 +78,34 @@ route_dispatch_ifinfo(struct link_ctx *lctx, struct rt_msghdr *rtm) if_free(ifp); } +#ifdef RTM_IFANNOUNCE +static void +route_dispatch_ifannounce(struct link_ctx *lctx, const struct rt_msghdr *rtm) +{ + struct ctx *ctx = lctx->link_ctx; + const struct if_announcemsghdr *ifan = (const struct if_announcemsghdr *)rtm; + struct interface *ifp; + + if (rtm->rtm_msglen < sizeof(*ifan)) + return; + + if (ifan->ifan_what != IFAN_DEPARTURE) + return; + + /* Interface has departed, remove it from consideration. */ + TAILQ_FOREACH(ifp, ctx->ctx_ifaces, if_next) { + if (ifp->if_index == ifan->ifan_index) + break; + } + if (ifp == NULL) + return; + + logwarnx("%s: interface has departed", ifp->if_name); + TAILQ_REMOVE(ctx->ctx_ifaces, ifp, if_next); + if_free(ifp); +} +#endif + static void route_dispatch(void *arg, unsigned short e) { @@ -105,6 +134,11 @@ route_dispatch(void *arg, unsigned short e) case RTM_IFINFO: route_dispatch_ifinfo(lctx, &rtm.hdr); break; +#ifdef RTM_IFANNOUNCE + case RTM_IFANNOUNCE: + route_dispatch_ifannounce(lctx, &rtm.hdr); + break; +#endif default: /* Ignore other messages */ break; @@ -185,4 +219,4 @@ link_free(struct ctx *ctx) close(lctx->link_fd); free(lctx); ctx->ctx_link = NULL; -} \ No newline at end of file +} From cba68a407e94563abc60eeb6513aad4bb78446f3 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Thu, 16 Apr 2026 22:18:01 +0100 Subject: [PATCH 11/19] Address revie comments. --- src/dhcp.c | 10 ++-------- src/route.c | 9 +++++---- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/dhcp.c b/src/dhcp.c index 149088c..8559d57 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -38,8 +38,6 @@ #include -#include "src/priv.h" - #ifdef AF_LINK #include #endif @@ -73,6 +71,7 @@ #include "if.h" #include "logerr.h" #include "plugin.h" +#include "priv.h" #ifdef HAVE_CASPER #include @@ -1302,8 +1301,7 @@ dhcp_handlebootp(struct interface *ifp, struct bootp *bootp, size_t len, break; wanted = dhcp_lease_findaddr(ctx, &paddr); if (wanted != NULL && !dhcp_lease_avail(lease, wanted, &now)) { - logwarnx( - "%s: plugin assigned address in-use: 0x%x %s", + logwarnx("%s: plugin assigned address in-use: 0x%x %s", ifp->if_name, bootp->xid, inet_ntoa(paddr)); #if 0 paddr.s_addr = INADDR_ANY; @@ -1665,12 +1663,8 @@ dhcp_readudp0(struct dhcp_ctx *ctx, int fd, unsigned short events) bootp = ctx->dhcp_udp_buf; ifp = if_findifpfromcmsg(ctx->dhcp_ctx, &msg, NULL); if (ifp == NULL) { - /* This is a common situation for me when my tap - * interfaces come and go. */ -#if 1 logwarnx("dhcp: interface not found 0x%x %s", bootp->xid, inet_ntoa(from.sin_addr)); -#endif return; } diff --git a/src/route.c b/src/route.c index 0cc019d..fcd8a6e 100644 --- a/src/route.c +++ b/src/route.c @@ -73,17 +73,18 @@ route_dispatch_ifinfo(struct link_ctx *lctx, struct rt_msghdr *rtm) if (ifp == NULL) return; - logwarnx("%s: interface is down", ifp->if_name); + loginfox("%s: interface is down", ifp->if_name); TAILQ_REMOVE(ctx->ctx_ifaces, ifp, if_next); if_free(ifp); } #ifdef RTM_IFANNOUNCE static void -route_dispatch_ifannounce(struct link_ctx *lctx, const struct rt_msghdr *rtm) +route_dispatch_ifannounce(struct link_ctx *lctx, const struct rt_msghdr *rtm) { struct ctx *ctx = lctx->link_ctx; - const struct if_announcemsghdr *ifan = (const struct if_announcemsghdr *)rtm; + const struct if_announcemsghdr *ifan = + (const struct if_announcemsghdr *)rtm; struct interface *ifp; if (rtm->rtm_msglen < sizeof(*ifan)) @@ -100,7 +101,7 @@ route_dispatch_ifannounce(struct link_ctx *lctx, const struct rt_msghdr *rtm) if (ifp == NULL) return; - logwarnx("%s: interface has departed", ifp->if_name); + loginfox("%s: interface has departed", ifp->if_name); TAILQ_REMOVE(ctx->ctx_ifaces, ifp, if_next); if_free(ifp); } From bde9992af40b6c138cee43d1518e0c93178c49f2 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Thu, 16 Apr 2026 22:21:34 +0100 Subject: [PATCH 12/19] Use ssize_t --- src/netlink.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/netlink.c b/src/netlink.c index a6b8e8b..6aecec0 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -62,15 +62,15 @@ netlink_get(struct link_ctx *lctx, struct iovec *iov, int fd, int flags, .msg_iov = iov, .msg_iovlen = 1, }; - size_t len; + ssize_t len; struct nlmsghdr *nlm; int r = 0; unsigned int again; bool terminated; recv_again: - len = (size_t)recvmsg(fd, &msg, flags); - if (len == 0 || (ssize_t)len == -1) + len = recvmsg(fd, &msg, flags); + if (len == 0 || len == -1) return (int)len; /* Check sender */ From 54d9a8274e399121679d590b99b0899c40a25faa Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Fri, 17 Apr 2026 09:03:34 +0100 Subject: [PATCH 13/19] Address more review comments --- src/dhcpsd.c | 2 +- src/netlink.c | 11 ++++++++--- src/route.c | 6 ++++-- src/unpriv.c | 4 +++- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/dhcpsd.c b/src/dhcpsd.c index 0b757d7..3e827ef 100644 --- a/src/dhcpsd.c +++ b/src/dhcpsd.c @@ -489,7 +489,6 @@ main(int argc, char **argv) if (!(ctx.ctx_options & DHCPSD_WAITIF)) { if_learnifaces(&ctx); - npools = 0; TAILQ_FOREACH(ifp, ctx.ctx_ifaces, if_next) { if (argc == 0) ifp->if_flags |= IF_ACTIVE; @@ -523,6 +522,7 @@ main(int argc, char **argv) goto exit; } + npools = 0; TAILQ_FOREACH(ifp, ctx.ctx_ifaces, if_next) { if (!(ifp->if_flags & IF_ACTIVE)) continue; diff --git a/src/netlink.c b/src/netlink.c index 6aecec0..72b57ed 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* - * dhcpsd - netlink(7) supprt + * dhcpsd - netlink(7) support * Copyright (c) 2025 Roy Marples * All rights reserved @@ -30,7 +30,6 @@ #include -#include #include #include #include @@ -117,7 +116,7 @@ netlink_get(struct link_ctx *lctx, struct iovec *iov, int fd, int flags, r = cb(lctx, nlm); } - if ((again || !terminated) && (lctx != NULL && lctx->link_fd != fd)) + if (again || !terminated) goto recv_again; return r; @@ -193,7 +192,9 @@ link_open(struct ctx *ctx) struct link_ctx *lctx = malloc(sizeof(*lctx)); struct sockaddr_nl nl = { .nl_family = AF_NETLINK, .nl_groups = RTMGRP_LINK }; +#ifdef NETLINK_BROADCAST_ERROR int on = 1; +#endif if (lctx == NULL) return -1; @@ -213,9 +214,13 @@ link_open(struct ctx *ctx) return -1; } + /* Routing socket can overflow if the kernel sends too many messages. + * We need to reliably track state and if we can't we need to know. */ +#ifdef NETLINK_BROADCAST_ERROR if (setsockopt(lctx->link_fd, SOL_NETLINK, NETLINK_BROADCAST_ERROR, &on, sizeof(on)) == -1) logerr("%s: NETLINK_BROADCAST_ERROR", __func__); +#endif if (eloop_event_add(ctx->ctx_eloop, lctx->link_fd, ELE_READ, netlink_handle, lctx) == -1) { diff --git a/src/route.c b/src/route.c index fcd8a6e..3359e5c 100644 --- a/src/route.c +++ b/src/route.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* - * dhcpsd - route(4) supprt + * dhcpsd - route(4) support * Copyright (c) 2025 Roy Marples * All rights reserved @@ -115,7 +115,7 @@ route_dispatch(void *arg, unsigned short e) ssize_t len; if (e != ELE_READ) { - logerrx("%s: unexpeced event %d", __func__, e); + logerrx("%s: unexpected event 0x%04x", __func__, e); } len = read(lctx->link_fd, &rtm, sizeof(rtm)); if (len == -1) { @@ -178,6 +178,8 @@ link_open(struct ctx *ctx) return -1; #ifdef SO_RERROR + /* Routing socket can overflow if the kernel sends too many messages. + * We need to reliably track state and if we can't we need to know. */ n = 1; if (setsockopt(lctx->link_fd, SOL_SOCKET, SO_RERROR, &n, sizeof(n)) == -1) diff --git a/src/unpriv.c b/src/unpriv.c index 6d422ee..eaffbeb 100644 --- a/src/unpriv.c +++ b/src/unpriv.c @@ -514,7 +514,9 @@ unpriv_dispatch_learnif(struct srv_ctx *sctx, const void *data, size_t len) goto err; } if_update(ifp, ifa->ifa_addr); - if_update_mtu(ifp); + /* Every interface must have an MTU */ + if (if_update_mtu(ifp) == -1) + goto err; err = 0; buf = ifp; From 192daa32c22ed231d226912aa812299f67b3b692 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Fri, 17 Apr 2026 14:10:10 +0100 Subject: [PATCH 14/19] Address more review comments. --- src/netlink.c | 2 +- src/unpriv.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/netlink.c b/src/netlink.c index 72b57ed..f405567 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -214,7 +214,7 @@ link_open(struct ctx *ctx) return -1; } - /* Routing socket can overflow if the kernel sends too many messages. + /* netlink socket can overflow if the kernel sends too many messages. * We need to reliably track state and if we can't we need to know. */ #ifdef NETLINK_BROADCAST_ERROR if (setsockopt(lctx->link_fd, SOL_NETLINK, NETLINK_BROADCAST_ERROR, &on, diff --git a/src/unpriv.c b/src/unpriv.c index eaffbeb..a6d4807 100644 --- a/src/unpriv.c +++ b/src/unpriv.c @@ -509,7 +509,7 @@ unpriv_dispatch_learnif(struct srv_ctx *sctx, const void *data, size_t len) goto err; } nlen = strlcpy(ifp->if_name, ifa->ifa_name, sizeof(ifp->if_name)); - if (nlen > sizeof(ifp->if_name)) { + if (nlen >= sizeof(ifp->if_name)) { errno = EINVAL; goto err; } From d897954fa84d1ba449f32f5d2b1f484a13260225 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Fri, 17 Apr 2026 15:58:44 +0100 Subject: [PATCH 15/19] Work with FreeBSD --- configure | 2 ++ src/dhcpsd.c | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/configure b/configure index 0bee3c8..7d0eeaa 100755 --- a/configure +++ b/configure @@ -594,6 +594,8 @@ $abort && exit 1 if [ -z "$LINK_SRC" ]; then printf "Testing for route ... " cat << EOF >_route.c +#include +#include #include int main(void) { struct rt_msghdr rtm = { .rtm_type = RTM_IFINFO }; diff --git a/src/dhcpsd.c b/src/dhcpsd.c index 3e827ef..82385d8 100644 --- a/src/dhcpsd.c +++ b/src/dhcpsd.c @@ -209,7 +209,7 @@ dhcpsd_fork(struct ctx *ctx) { int fork_fd[2]; pid_t pid; -#ifdef HAVE_CASPER +#ifdef HAVE_CASPERx cap_rights_t rights; #endif @@ -227,7 +227,8 @@ dhcpsd_fork(struct ctx *ctx) case 0: ctx->ctx_fork_fd = fork_fd[1]; close(fork_fd[0]); -#ifdef HAVE_CASPER +#ifdef HAVE_CASPERx + /* XXX This is failing on FreeBSD-15 */ cap_rights_init(&rights, CAP_WRITE); if (caph_rights_limit(ctx->ctx_fork_fd, &rights) == -1) { logerr("%s: caph_rights_limit", __func__); @@ -274,7 +275,7 @@ dhcpsd_fork(struct ctx *ctx) ctx->ctx_fork_fd = fork_fd[0]; close(fork_fd[1]); -#ifdef HAVE_CASPER +#ifdef HAVE_CASPERx cap_rights_init(&rights, CAP_READ); if (caph_rights_limit(ctx->ctx_fork_fd, &rights) == -1) { logerr("%s: caph_rights_limit", __func__); From e0780a27c560e4504cabbf7cf6dd5965403186b4 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Fri, 17 Apr 2026 15:59:09 +0100 Subject: [PATCH 16/19] Learn IF MTU without -w --- src/if.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/if.c b/src/if.c index 3688bec..1090ee4 100644 --- a/src/if.c +++ b/src/if.c @@ -232,6 +232,11 @@ if_learnifaces(struct ctx *ctx) if_update(ifp, ifa->ifa_addr); if_update_output(ifp); + if (if_update_mtu(ifp) == -1) { + logerr("%s: if_update_mtu: %s", __func__, ifa->ifa_name); + free(ifp); + continue; + } TAILQ_INSERT_TAIL(ctx->ctx_ifaces, ifp, if_next); } From 362e90f0cdfe275c9d772d5c0bf7c5dcd3cbec45 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Sun, 19 Apr 2026 10:30:32 +0100 Subject: [PATCH 17/19] Fix compile on OpenBSD. --- src/route.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/route.c b/src/route.c index 3359e5c..27691e8 100644 --- a/src/route.c +++ b/src/route.c @@ -193,7 +193,7 @@ link_open(struct ctx *ctx) #elif defined(ROUTE_MSGFILTER) /* Convert the array into a bitmask. */ msgfilter_mask = 0; - for (i = 0; i < __arraycount(msgfilter); i++) + for (i = 0; i < ARRAYCOUNT(msgfilter); i++) msgfilter_mask |= ROUTE_FILTER(msgfilter[i]); if (setsockopt(lctx->link_fd, PF_ROUTE, ROUTE_MSGFILTER, &msgfilter_mask, sizeof(msgfilter_mask)) == -1) From d9612d9882b6a260a792439eaea97287fc4cdbc6 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Tue, 21 Apr 2026 11:21:32 +0100 Subject: [PATCH 18/19] Address more review comments --- configure | 3 +-- src/netlink.c | 7 +++++-- src/priv.c | 1 - src/route.c | 10 +++++++--- src/unpriv.c | 9 ++++++--- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/configure b/configure index 7d0eeaa..f06d393 100755 --- a/configure +++ b/configure @@ -317,7 +317,7 @@ int main(void) { return 0; } EOF - if $CC -fsanitize=address _test.c -o _test 2>&3; then + if $CC -fsanitize=address,undefined _test.c -o _test 2>&3; then echo "yes" echo "SANITIZEADDRESS= yes" >>$CONFIG_MK echo "CPPFLAGS+= -DASAN" >>$CONFIG_MK @@ -614,7 +614,6 @@ fi if [ -z "$LINK_SRC" ]; then printf "Testing for netlink ... " cat << EOF >_netlink.c -#include #include #include #include diff --git a/src/netlink.c b/src/netlink.c index f405567..efab896 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -151,8 +151,7 @@ netlink_link(struct link_ctx *lctx, struct nlmsghdr *nlm) loginfox("%s: interface has departed", ifp->if_name); else if (!(ifi->ifi_flags & IFF_UP)) loginfox("%s: interface is down", ifp->if_name); - else - logerrx("%s: interface going away for no reason", ifp->if_name); + TAILQ_REMOVE(ctx->ctx_ifaces, ifp, if_next); if_free(ifp); return 0; @@ -179,7 +178,11 @@ netlink_handle(void *arg, unsigned short e) .iov_len = sizeof(buf), }; +#ifdef NDEBUG UNUSED(e); +#else + assert(e == ELE_READ); +#endif if (netlink_get(lctx, &iov, lctx->link_fd, MSG_DONTWAIT, netlink_dispatch) == -1) diff --git a/src/priv.c b/src/priv.c index 610361a..e74a561 100644 --- a/src/priv.c +++ b/src/priv.c @@ -43,7 +43,6 @@ #include "priv.h" #include "queue.h" #include "service.h" -#include "src/eloop.h" #define P_OPENBPF 1 #define P_SENDBPF 2 diff --git a/src/route.c b/src/route.c index 27691e8..822215a 100644 --- a/src/route.c +++ b/src/route.c @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -114,9 +115,12 @@ route_dispatch(void *arg, unsigned short e) struct rtm rtm; ssize_t len; - if (e != ELE_READ) { - logerrx("%s: unexpected event 0x%04x", __func__, e); - } +#ifdef NDEBUG + UNUSED(e); +#else + assert(e == ELE_READ); +#endif + len = read(lctx->link_fd, &rtm, sizeof(rtm)); if (len == -1) { logerr("%s: read", __func__); diff --git a/src/unpriv.c b/src/unpriv.c index a6d4807..d970557 100644 --- a/src/unpriv.c +++ b/src/unpriv.c @@ -98,10 +98,10 @@ unpriv_getifaddrs(struct srv_ctx *ctx, struct ifaddrs **ifahead, size_t rdata_len, len; int err; - err = srv_run(ctx, 0, U_GETIFADDRS, match_if_index, + err = srv_run(ctx, NULL, U_GETIFADDRS, match_if_index, match_if_index != NULL ? sizeof(*match_if_index) : 0, &result, &rdata, &rdata_len); - if (err == -1) + if (err == -1 || result == -1) return -1; /* Should be impossible - lo0 will always exist. */ @@ -258,11 +258,14 @@ unpriv_getaddrinfo(struct srv_ctx *ctx, const char *hostname, u_gai.u_gai_hints.u_ai_protocol = hints->ai_protocol; } - err = srv_run(ctx, 0, U_GETADDRINFO, &u_gai, sizeof(u_gai), &result, + err = srv_run(ctx, NULL, U_GETADDRINFO, &u_gai, sizeof(u_gai), &result, &rdata, &rdata_len); if (err == -1) return -1; + if (result != 0) + return (int)result; + for (u_ai = rdata; rdata_len != 0; rdata_len -= sizeof(*u_ai), u_ai++) { if (rdata_len < sizeof(*u_ai)) { logerrx("%s: ai_addrinfo truncated", __func__); From bfb0a400811c6fa5f7daf452f80afddd0efb0cbc Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Tue, 21 Apr 2026 11:24:32 +0100 Subject: [PATCH 19/19] Add missing include --- src/netlink.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/netlink.c b/src/netlink.c index efab896..59a2f28 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -30,6 +30,7 @@ #include +#include #include #include #include