From d41c505981e1e8f493c544dff004a203fde42973 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Mon, 6 Oct 2025 12:30:21 +0100 Subject: [PATCH 1/9] Protocols will notify when dhcpcd can exit DHCPv6 RELEASE requires the addresses to be dropped before a RELEASE message is sent. We now wait for an acknowledgement or a timeout before notifying that DHCPv6 has stopped for the interface. DHCPv4 RELEASE is the other way around, there is no acknowledgement. So we wait for 1 second after sending the message before removing the address and notifying DHCP has stopped for the interface. If we are not releasing then we notify dhcpcd that the protocol has stopped right away when we drop the lease. dhcpcd will exit once there are no running protocols for the interfaces. Fixes #513. Hopefully #535, #519 and #509 as well. --- src/dhcp.c | 88 +++++++++++++++++++++++------------- src/dhcp6.c | 20 +++++---- src/dhcpcd.c | 125 +++++++++++++++++++++++++++++++++++++-------------- src/dhcpcd.h | 1 + src/ipv4ll.c | 4 ++ src/ipv6nd.c | 1 + src/route.c | 3 +- 7 files changed, 169 insertions(+), 73 deletions(-) diff --git a/src/dhcp.c b/src/dhcp.c index c4de0e4f..d756082e 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -2866,6 +2866,43 @@ dhcp_reboot(struct interface *ifp) send_request(ifp); } +static void +dhcp_deconfigure(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); + struct if_options *ifo = ifp->options; + +#ifdef AUTH + dhcp_auth_reset(&state->auth); +#endif + + state->state = DHS_NONE; + free(state->offer); + state->offer = NULL; + state->offer_len = 0; + free(state->old); + state->old = state->new; + state->old_len = state->new_len; + state->new = NULL; + state->new_len = 0; + if (ifo->options & DHCPCD_CONFIGURE) + ipv4_applyaddr(ifp); + else { + state->addr = NULL; + state->added = 0; + script_runreason(ifp, state->reason); + } + free(state->old); + state->old = NULL; + state->old_len = 0; + state->lease.addr.s_addr = 0; + ifo->options &= ~(DHCPCD_CSR_WARNED | DHCPCD_ROUTER_HOST_ROUTE_WARNED); + + dhcp_free(ifp); + dhcpcd_dropped(ifp); +} + void dhcp_drop(struct interface *ifp, const char *reason) { @@ -2876,6 +2913,7 @@ dhcp_drop(struct interface *ifp, const char *reason) * but we do have a timeout, so punt it. */ if (state == NULL || state->state == DHS_NONE) { eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + dhcpcd_dropped(ifp); return; } @@ -2886,6 +2924,7 @@ dhcp_drop(struct interface *ifp, const char *reason) #ifdef ARPING state->arping_index = -1; #endif + state->reason = reason; if (ifo->options & DHCPCD_RELEASE && !(ifo->options & DHCPCD_INFORM)) { /* Failure to send the release may cause this function to @@ -2899,10 +2938,21 @@ dhcp_drop(struct interface *ifp, const char *reason) state->new != NULL && state->lease.server.s_addr != INADDR_ANY) { + /* We need to delay removal of the IP address so the + * message can be sent. + * Unlike DHCPv6, there is no acknowledgement. */ + const struct timespec delay = { + .tv_sec = 1, + }; + loginfox("%s: releasing lease of %s", ifp->name, inet_ntoa(state->lease.addr)); dhcp_new_xid(ifp); send_message(ifp, DHCP_RELEASE, NULL); + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + eloop_timeout_add_tv(ifp->ctx->eloop, + &delay, dhcp_deconfigure, ifp); + return; } } #ifdef AUTH @@ -2919,36 +2969,7 @@ dhcp_drop(struct interface *ifp, const char *reason) #endif eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); -#ifdef AUTH - dhcp_auth_reset(&state->auth); -#endif - - state->state = DHS_NONE; - free(state->offer); - state->offer = NULL; - state->offer_len = 0; - free(state->old); - state->old = state->new; - state->old_len = state->new_len; - state->new = NULL; - state->new_len = 0; - state->reason = reason; - if (ifo->options & DHCPCD_CONFIGURE) - ipv4_applyaddr(ifp); - else { - state->addr = NULL; - state->added = 0; - script_runreason(ifp, state->reason); - } - free(state->old); - state->old = NULL; - state->old_len = 0; - state->lease.addr.s_addr = 0; - ifo->options &= ~(DHCPCD_CSR_WARNED | DHCPCD_ROUTER_HOST_ROUTE_WARNED); - - /* Close DHCP ports so a changed interface family is picked - * up by a new BPF state. */ - dhcp_close(ifp); + dhcp_deconfigure(ifp); } static int @@ -3108,6 +3129,12 @@ dhcp_handledhcp(struct interface *ifp, struct bootp *bootp, size_t bootp_len, #define IS_STATE_ACTIVE(s) ((s)-state != DHS_NONE && \ (s)->state != DHS_INIT && (s)->state != DHS_BOUND) + /* Don't do anything if the user hasn't configured it. */ + if (ifp->active != IF_ACTIVE_USER || + ifp->options->options & DHCPCD_STOPPING || + !(ifp->options->options & DHCPCD_DHCP)) + return; + if (bootp->op != BOOTREPLY) { if (IS_STATE_ACTIVE(state)) logdebugx("%s: op (%d) is not BOOTREPLY", @@ -3932,6 +3959,7 @@ dhcp_free(struct interface *ifp) free(state->offer); free(state->clientid); free(state); + ifp->if_data[IF_DATA_DHCP] = NULL; } ctx = ifp->ctx; diff --git a/src/dhcp6.c b/src/dhcp6.c index dbe73de2..4c4b7b5a 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -2113,26 +2113,22 @@ dhcp6_startrelease(struct interface *ifp) if (state->state != DH6S_BOUND) return; + /* RFC8415 18.2.7 says we must stop using the addresses before + * we send the release message. */ + dhcp6_freedrop_addrs(ifp, 0, IPV6_AF_DELEGATED, NULL); + state->state = DH6S_RELEASE; state->RTC = 0; state->IMD = REL_MAX_DELAY; state->IRT = REL_TIMEOUT; state->MRT = REL_MAX_RT; - /* MRC of REL_MAX_RC is optional in RFC 3315 18.1.6 */ -#if 0 state->MRC = REL_MAX_RC; state->MRCcallback = dhcp6_finishrelease; -#else - state->MRC = 0; - state->MRCcallback = NULL; -#endif if (dhcp6_makemessage(ifp) == -1) logerr("%s: %s", __func__, ifp->name); - else { + else dhcp6_sendrelease(ifp); - dhcp6_finishrelease(ifp); - } } static int @@ -3610,6 +3606,11 @@ dhcp6_recvif(struct interface *ifp, const char *sfrom, ifp->name, sfrom); dhcp6_fail(ifp, true); return; + case DH6S_RELEASE: + loginfox("%s: %s acknowledged RELEASE6", + ifp->name, sfrom); + dhcp6_finishrelease(ifp); + return; default: valid_op = false; break; @@ -4293,6 +4294,7 @@ dhcp6_freedrop(struct interface *ifp, int drop, const char *reason) free(state); ifp->if_data[IF_DATA_DHCP6] = NULL; } + dhcpcd_dropped(ifp); /* If we don't have any more DHCP6 enabled interfaces, * close the global socket and release resources */ diff --git a/src/dhcpcd.c b/src/dhcpcd.c index 5fdd7c1c..1f479b1d 100644 --- a/src/dhcpcd.c +++ b/src/dhcpcd.c @@ -438,27 +438,60 @@ dhcpcd_drop(struct interface *ifp, int stop) dhcpcd_drop_af(ifp, stop, AF_UNSPEC); } -static void -stop_interface(struct interface *ifp, const char *reason) +static bool +dhcpcd_ifrunning(struct interface *ifp) { - struct dhcpcd_ctx *ctx; - ctx = ifp->ctx; - loginfox("%s: removing interface", ifp->name); - ifp->options->options |= DHCPCD_STOPPING; +#ifdef INET + if (D_CSTATE(ifp) != NULL) + return true; +#ifdef IPV4LL + if (IPV4LL_CSTATE(ifp) != NULL) + return true; +#endif +#endif +#ifdef DHCP6 + if (D6_CSTATE(ifp) != NULL) + return true; +#endif + return false; +} - dhcpcd_drop(ifp, 1); - script_runreason(ifp, reason == NULL ? "STOPPED" : reason); +void +dhcpcd_dropped(struct interface *ifp) +{ + struct dhcpcd_ctx *ctx = ifp->ctx; - /* Delete all timeouts for the interfaces */ - eloop_q_timeout_delete(ctx->eloop, ELOOP_QUEUE_ALL, NULL, ifp); + if (dhcpcd_ifrunning(ifp)) + return; /* De-activate the interface */ - ifp->active = IF_INACTIVE; - ifp->options->options &= ~DHCPCD_STOPPING; + if (ifp->active) { + ifp->active = IF_INACTIVE; + ifp->options->options &= ~DHCPCD_STOPPING; + script_runreason(ifp, "STOPPED"); + } - if (!(ctx->options & (DHCPCD_MANAGER | DHCPCD_TEST))) - eloop_exit(ctx->eloop, EXIT_FAILURE); + if (!(ctx->options & DHCPCD_EXITING)) + return; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if (dhcpcd_ifrunning(ifp)) + break; + } + + /* All interfaces have stopped, we can exit */ + if (ifp == NULL) + eloop_exit(ctx->eloop, EXIT_SUCCESS); +} + +static void +stop_interface(struct interface *ifp) +{ + + loginfox("%s: removing interface", ifp->name); + ifp->options->options |= DHCPCD_STOPPING; + dhcpcd_drop(ifp, 1); } static void @@ -744,6 +777,9 @@ dhcpcd_handlecarrier(struct interface *ifp, int carrier, unsigned int flags) ifp->carrier = carrier; ifp->flags = flags; + if (ifp->options->options & DHCPCD_STOPPING) + return; + if (!if_is_link_up(ifp)) { if (!ifp->active || (!was_link_up && !was_roaming)) return; @@ -1070,13 +1106,16 @@ dhcpcd_handleinterface(void *arg, int action, const char *ifname) } if (ifp->active) { logdebugx("%s: interface departed", ifp->name); - stop_interface(ifp, "DEPARTED"); + stop_interface(ifp); } TAILQ_REMOVE(ctx->ifaces, ifp, next); if_free(ifp); return 0; } + if (ctx->options & DHCPCD_EXITING) + return 0; + ifs = if_discover(ctx, &ifaddrs, -1, UNCONST(argv)); if (ifs == NULL) { logerr(__func__); @@ -1138,6 +1177,9 @@ dhcpcd_handlelink(void *arg, unsigned short events) { struct dhcpcd_ctx *ctx = arg; + if (ctx->options & DHCPCD_EXITING) + return; + if (events != ELE_READ) logerrx("%s: unexpected event 0x%04x", __func__, events); @@ -1378,14 +1420,15 @@ reconf_reboot(struct dhcpcd_ctx *ctx, int action, int argc, char **argv, int oi) } } -static void +static bool stop_all_interfaces(struct dhcpcd_ctx *ctx, unsigned long long opts) { struct interface *ifp; + bool anystopped = false; ctx->options |= opts; if (ctx->ifaces == NULL) - return; + return anystopped; if (ctx->options & DHCPCD_RELEASE) ctx->options &= ~DHCPCD_PERSISTENT; @@ -1398,8 +1441,10 @@ stop_all_interfaces(struct dhcpcd_ctx *ctx, unsigned long long opts) if (ifp->options->options & DHCPCD_RELEASE) ifp->options->options &= ~DHCPCD_PERSISTENT; ifp->options->options |= DHCPCD_EXITING; - stop_interface(ifp, NULL); + anystopped = true; + stop_interface(ifp); } + return anystopped; } static void @@ -1437,7 +1482,6 @@ dhcpcd_renew(struct dhcpcd_ctx *ctx) #ifdef USE_SIGNALS #define sigmsg "received %s, %s" -static volatile bool dhcpcd_exiting = false; void dhcpcd_signal_cb(int sig, void *arg) { @@ -1517,14 +1561,18 @@ dhcpcd_signal_cb(int sig, void *arg) * During teardown we don't want to process SIGTERM or SIGINT again, * as that could trigger memory issues. */ - if (dhcpcd_exiting) + if (ctx->options & DHCPCD_EXITING) + return; + + ctx->options |= DHCPCD_EXITING; + if (!(ctx->options & DHCPCD_TEST) && + stop_all_interfaces(ctx, opts)) + { + /* We stopped something, we will exit once that is done. */ return; + } - dhcpcd_exiting = true; - if (!(ctx->options & DHCPCD_TEST)) - stop_all_interfaces(ctx, opts); eloop_exit(ctx->eloop, exit_code); - dhcpcd_exiting = false; } #endif @@ -1664,8 +1712,10 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, if (opts & (DHCPCD_EXITING | DHCPCD_RELEASE)) { if (oifind == argc && af == AF_UNSPEC) { - stop_all_interfaces(ctx, opts); - eloop_exit(ctx->eloop, EXIT_SUCCESS); + if (stop_all_interfaces(ctx, opts) == false) + eloop_exit(ctx->eloop, EXIT_SUCCESS); + /* We did stop an interface, it will notify us once + * dropped so we can exit. */ return 0; } @@ -1695,7 +1745,7 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, if (af != AF_UNSPEC) dhcpcd_drop_af(ifp, 1, af); else - stop_interface(ifp, NULL); + stop_interface(ifp); ifo->options = orig_opts; } return 0; @@ -1897,17 +1947,24 @@ dhcpcd_pidfile_timeout(void *arg) pid_t pid; pid = pidfile_read(ctx->pidfile); - - if(pid == -1) + if (pid == -1) eloop_exit(ctx->eloop, EXIT_SUCCESS); - else if (++ctx->duid_len >= 100) { /* overload duid_len */ - logerrx("pid %d failed to exit", (int)pid); - eloop_exit(ctx->eloop, EXIT_FAILURE); - } else + else eloop_timeout_add_msec(ctx->eloop, 100, dhcpcd_pidfile_timeout, ctx); } +static void +dhcpcd_exit_timeout(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + pid_t pid; + + pid = pidfile_read(ctx->pidfile); + logwarnx("pid %lld failed to exit", (long long)pid); + eloop_exit(ctx->eloop, EXIT_FAILURE); +} + static int dup_null(int fd) { int fd_null = open(_PATH_DEVNULL, O_WRONLY); @@ -2267,6 +2324,8 @@ main(int argc, char **argv, char **envp) /* Spin until it exits */ loginfox("waiting for pid %d to exit", (int)pid); dhcpcd_pidfile_timeout(&ctx); + eloop_timeout_add_sec(ctx.eloop, 50, + dhcpcd_exit_timeout, &ctx); goto run_loop; } } diff --git a/src/dhcpcd.h b/src/dhcpcd.h index 527399d3..c4dbe999 100644 --- a/src/dhcpcd.h +++ b/src/dhcpcd.h @@ -267,6 +267,7 @@ void dhcpcd_handlecarrier(struct interface *, int, unsigned int); int dhcpcd_handleinterface(void *, int, const char *); void dhcpcd_handlehwaddr(struct interface *, uint16_t, const void *, uint8_t); void dhcpcd_dropinterface(struct interface *, const char *); +void dhcpcd_dropped(struct interface *); int dhcpcd_selectprofile(struct interface *, const char *); void dhcpcd_startinterface(void *); diff --git a/src/ipv4ll.c b/src/ipv4ll.c index c2c7a8e7..4dc3ba7c 100644 --- a/src/ipv4ll.c +++ b/src/ipv4ll.c @@ -448,6 +448,7 @@ ipv4ll_drop(struct interface *ifp) assert(ifp != NULL); + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); ipv4ll_freearp(ifp); if ((ifp->options->options & DHCPCD_NODROP) == DHCPCD_NODROP) @@ -481,6 +482,9 @@ ipv4ll_drop(struct interface *ifp) rt_build(ifp->ctx, AF_INET); script_runreason(ifp, "IPV4LL"); } + + ipv4ll_free(ifp); + dhcpcd_dropped(ifp); } void diff --git a/src/ipv6nd.c b/src/ipv6nd.c index 09e0d16e..c8c18fc4 100644 --- a/src/ipv6nd.c +++ b/src/ipv6nd.c @@ -1927,6 +1927,7 @@ ipv6nd_recvmsg(struct dhcpcd_ctx *ctx, struct msghdr *msg) /* Don't do anything if the user hasn't configured it. */ if (ifp->active != IF_ACTIVE_USER || + ifp->options->options & DHCPCD_STOPPING || !(ifp->options->options & DHCPCD_IPV6)) return; diff --git a/src/route.c b/src/route.c index 32d132b1..7861546e 100644 --- a/src/route.c +++ b/src/route.c @@ -824,7 +824,8 @@ rt_build(struct dhcpcd_ctx *ctx, int af) } #ifdef BSD - if (if_missfilter_apply(ctx) == -1 && errno != ENOTSUP) + if (!(ctx->options & DHCPCD_EXITING) && + if_missfilter_apply(ctx) == -1 && errno != ENOTSUP) logerr("if_missfilter_apply"); #endif From 2ea8ec03b6fb6183561137ffe7a5215a780ea105 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Mon, 6 Oct 2025 13:16:15 +0100 Subject: [PATCH 2/9] Minor improvements. --- src/dhcp6.c | 6 ++++-- src/dhcpcd.c | 8 ++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/dhcp6.c b/src/dhcp6.c index 4c4b7b5a..db73182f 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -2125,9 +2125,11 @@ dhcp6_startrelease(struct interface *ifp) state->MRC = REL_MAX_RC; state->MRCcallback = dhcp6_finishrelease; - if (dhcp6_makemessage(ifp) == -1) + if (dhcp6_makemessage(ifp) == -1) { logerr("%s: %s", __func__, ifp->name); - else + /* not much we can do apart from finish now */ + dhcp6_finishrelease(ifp); + } else dhcp6_sendrelease(ifp); } diff --git a/src/dhcpcd.c b/src/dhcpcd.c index 1f479b1d..60e3b347 100644 --- a/src/dhcpcd.c +++ b/src/dhcpcd.c @@ -1961,8 +1961,12 @@ dhcpcd_exit_timeout(void *arg) pid_t pid; pid = pidfile_read(ctx->pidfile); - logwarnx("pid %lld failed to exit", (long long)pid); - eloop_exit(ctx->eloop, EXIT_FAILURE); + if (pid == -1) + eloop_exit(ctx->eloop, EXIT_SUCCESS); + else { + logwarnx("pid %lld failed to exit", (long long)pid); + eloop_exit(ctx->eloop, EXIT_FAILURE); + } } static int dup_null(int fd) From 7ef3ba171c855f57df4626eee345f6a257da2d51 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Mon, 6 Oct 2025 13:45:10 +0100 Subject: [PATCH 3/9] Fix some fallout. --- src/dhcp.c | 7 +++++-- src/dhcpcd.c | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/dhcp.c b/src/dhcp.c index d756082e..a37ccfd7 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -2899,8 +2899,11 @@ dhcp_deconfigure(void *arg) state->lease.addr.s_addr = 0; ifo->options &= ~(DHCPCD_CSR_WARNED | DHCPCD_ROUTER_HOST_ROUTE_WARNED); - dhcp_free(ifp); - dhcpcd_dropped(ifp); + if (ifo->options & DHCPCD_STOPPING) { + dhcp_free(ifp); + dhcpcd_dropped(ifp); + } else + dhcp_close(ifp); } void diff --git a/src/dhcpcd.c b/src/dhcpcd.c index 60e3b347..fb289e5b 100644 --- a/src/dhcpcd.c +++ b/src/dhcpcd.c @@ -462,7 +462,9 @@ dhcpcd_dropped(struct interface *ifp) { struct dhcpcd_ctx *ctx = ifp->ctx; - if (dhcpcd_ifrunning(ifp)) + if (ifp->options == NULL || + !(ifp->options->options & DHCPCD_STOPPING) || + dhcpcd_ifrunning(ifp)) return; /* De-activate the interface */ From 7b15c54ddaa5c3b2672452ceff093770ee088a1f Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Mon, 6 Oct 2025 15:16:43 +0100 Subject: [PATCH 4/9] Run and document RELEASE. --- hooks/dhcpcd-run-hooks.8.in | 4 +++- src/dhcp.c | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/hooks/dhcpcd-run-hooks.8.in b/hooks/dhcpcd-run-hooks.8.in index 73859b37..4bed386b 100644 --- a/hooks/dhcpcd-run-hooks.8.in +++ b/hooks/dhcpcd-run-hooks.8.in @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd October 11, 2024 +.Dd October 6, 2025 .Dt DHCPCD-RUN-HOOKS 8 .Os .Sh NAME @@ -105,6 +105,8 @@ dhcpcd renewed its lease. dhcpcd has rebound to a new DHCP server. .It Dv REBOOT | Dv REBOOT6 dhcpcd successfully requested a lease from a DHCP server. +.It Dv RELEASE | Dv RELEASE6 +dhcpcd has released the lease. .It Dv DELEGATED6 dhcpcd assigned a delegated prefix to the interface. .It Dv IPV4LL diff --git a/src/dhcp.c b/src/dhcp.c index a37ccfd7..89be71a5 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -2872,11 +2872,16 @@ dhcp_deconfigure(void *arg) struct interface *ifp = arg; struct dhcp_state *state = D_STATE(ifp); struct if_options *ifo = ifp->options; + const char *reason; #ifdef AUTH dhcp_auth_reset(&state->auth); #endif + if (state->state == DHS_RELEASE) + reason = "RELEASE"; + else + reason = state->reason; state->state = DHS_NONE; free(state->offer); state->offer = NULL; @@ -2891,8 +2896,8 @@ dhcp_deconfigure(void *arg) else { state->addr = NULL; state->added = 0; - script_runreason(ifp, state->reason); } + script_runreason(ifp, reason); free(state->old); state->old = NULL; state->old_len = 0; From 5738d9cbdd7c217094cdb427b91f44892a42acbc Mon Sep 17 00:00:00 2001 From: "Sime Zupanovic (EXT)" Date: Thu, 16 Oct 2025 17:27:30 +0100 Subject: [PATCH 5/9] Check context options if no interface options are setup. If allows an interface to stop even if dhcpcd is not. --- src/dhcpcd.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/dhcpcd.c b/src/dhcpcd.c index fb289e5b..849e538b 100644 --- a/src/dhcpcd.c +++ b/src/dhcpcd.c @@ -779,7 +779,15 @@ dhcpcd_handlecarrier(struct interface *ifp, int carrier, unsigned int flags) ifp->carrier = carrier; ifp->flags = flags; - if (ifp->options->options & DHCPCD_STOPPING) + /* + * Inactive interfaces may not have options, we so check the + * global context if we are stopping or not. + * This allows an active interface to stop even if dhcpcd is not. + */ + if (ifp->options != NULL) { + if (ifp->options->options & DHCPCD_STOPPING) + return; + } else if (ifp->ctx->options & DHCPCD_STOPPING) return; if (!if_is_link_up(ifp)) { From 93305c28505601fda572912d97a9396b341f8909 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Mon, 3 Nov 2025 14:33:17 +0000 Subject: [PATCH 6/9] DHCP6: release cleanly While here address some hangup issues. --- src/dhcp6.c | 8 +++----- src/dhcpcd.c | 3 --- src/privsep.c | 5 +++++ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/dhcp6.c b/src/dhcp6.c index db73182f..2cca6e6d 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -2110,12 +2110,10 @@ dhcp6_startrelease(struct interface *ifp) struct dhcp6_state *state; state = D6_STATE(ifp); - if (state->state != DH6S_BOUND) + if (state->state != DH6S_BOUND) { + dhcp6_finishrelease(ifp); return; - - /* RFC8415 18.2.7 says we must stop using the addresses before - * we send the release message. */ - dhcp6_freedrop_addrs(ifp, 0, IPV6_AF_DELEGATED, NULL); + } state->state = DH6S_RELEASE; state->RTC = 0; diff --git a/src/dhcpcd.c b/src/dhcpcd.c index 849e538b..be95ecbd 100644 --- a/src/dhcpcd.c +++ b/src/dhcpcd.c @@ -1187,9 +1187,6 @@ dhcpcd_handlelink(void *arg, unsigned short events) { struct dhcpcd_ctx *ctx = arg; - if (ctx->options & DHCPCD_EXITING) - return; - if (events != ELE_READ) logerrx("%s: unexpected event 0x%04x", __func__, events); diff --git a/src/privsep.c b/src/privsep.c index 1fff0798..76668fb2 100644 --- a/src/privsep.c +++ b/src/privsep.c @@ -1136,6 +1136,10 @@ ps_recvpsmsg(struct dhcpcd_ctx *ctx, int fd, unsigned short events, struct msghdr msg = { .msg_iov = iov, .msg_iovlen = 1 }; bool stop = false; + if (events & ELE_HANGUP) { + len = 0; + goto stop; + } if (!(events & ELE_READ)) logerrx("%s: unexpected event 0x%04x", __func__, events); @@ -1163,6 +1167,7 @@ ps_recvpsmsg(struct dhcpcd_ctx *ctx, int fd, unsigned short events, } if (stop) { +stop: ctx->options |= DHCPCD_EXITING; #ifdef PRIVSEP_DEBUG logdebugx("process %d stopping", getpid()); From 5f92561b93e56710af21bbbad580e4ab0d4e34b9 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Mon, 3 Nov 2025 14:56:39 +0000 Subject: [PATCH 7/9] IPv4LL: free on nodrop --- src/ipv4ll.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ipv4ll.c b/src/ipv4ll.c index 4dc3ba7c..c16a1dca 100644 --- a/src/ipv4ll.c +++ b/src/ipv4ll.c @@ -452,7 +452,7 @@ ipv4ll_drop(struct interface *ifp) ipv4ll_freearp(ifp); if ((ifp->options->options & DHCPCD_NODROP) == DHCPCD_NODROP) - return; + goto free; state = IPV4LL_STATE(ifp); if (state) { @@ -483,6 +483,7 @@ ipv4ll_drop(struct interface *ifp) script_runreason(ifp, "IPV4LL"); } +free: ipv4ll_free(ifp); dhcpcd_dropped(ifp); } From 3134f25d1b3b65631fe61d2b4599ebd31e0b7d52 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Mon, 3 Nov 2025 19:16:25 +0000 Subject: [PATCH 8/9] eloop: support epoll_pwait2 for kernels >= 5.11 As it more matches ppoll semantics. Note that epoll_pwait does NOT work with zero events, so use ppoll in this case. --- src/eloop.c | 45 ++++++++++++++++++++++++++++++++------------- src/privsep-linux.c | 3 +++ 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/eloop.c b/src/eloop.c index ded1624e..c9ca8e70 100644 --- a/src/eloop.c +++ b/src/eloop.c @@ -50,8 +50,13 @@ #define _kevent kevent #endif #elif defined(__linux__) +#include #include +#include #define USE_EPOLL +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) +#define HAVE_EPOLL_PWAIT2 +#endif #else #include #define USE_PPOLL @@ -925,24 +930,38 @@ eloop_run_kqueue(struct eloop *eloop, const struct timespec *ts) static int eloop_run_epoll(struct eloop *eloop, const struct timespec *ts) { - int timeout, n, nn; + int n, nn; struct epoll_event *epe; struct eloop_event *e; unsigned short events; - if (ts != NULL) { - if (ts->tv_sec > INT_MAX / 1000 || - (ts->tv_sec == INT_MAX / 1000 && - ((ts->tv_nsec + 999999) / 1000000 > INT_MAX % 1000000))) - timeout = INT_MAX; - else - timeout = (int)(ts->tv_sec * 1000 + - (ts->tv_nsec + 999999) / 1000000); - } else - timeout = -1; + /* epoll does not work with zero events */ + if (eloop->nfds == 0) + n = ppoll(NULL, 0, ts, &eloop->sigset); + else +#ifdef HAVE_EPOLL_PWAIT2 + n = epoll_pwait2(eloop->fd, eloop->fds, (int)eloop->nfds, + ts, &eloop->sigset); +#else + { + int timeout; + + if (ts != NULL) { + if (ts->tv_sec > INT_MAX / 1000 || + (ts->tv_sec == INT_MAX / 1000 && + ((ts->tv_nsec + 999999) / 1000000 > + INT_MAX % 1000000))) + timeout = INT_MAX; + else + timeout = (int)(ts->tv_sec * 1000 + + (ts->tv_nsec + 999999) / 1000000); + } else + timeout = -1; - n = epoll_pwait(eloop->fd, eloop->fds, (int)eloop->nfds, timeout, - &eloop->sigset); + n = epoll_pwait(eloop->fd, eloop->fds, (int)eloop->nfds, + timeout, &eloop->sigset); + } +#endif if (n == -1) return -1; diff --git a/src/privsep-linux.c b/src/privsep-linux.c index 036a35fe..43470a2d 100644 --- a/src/privsep-linux.c +++ b/src/privsep-linux.c @@ -325,6 +325,9 @@ static struct sock_filter ps_seccomp_filter[] = { #ifdef __NR_epoll_pwait SECCOMP_ALLOW(__NR_epoll_pwait), #endif +#ifdef __NR_epoll_pwait2 + SECCOMP_ALLOW(__NR_epoll_pwait2), +#endif #ifdef __NR_exit_group SECCOMP_ALLOW(__NR_exit_group), #endif From 4dce5d43384138d57adda9f21bb5aae92d3f6134 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Tue, 4 Nov 2025 09:18:57 +0000 Subject: [PATCH 9/9] privsep: test eloop error correctly --- src/privsep.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/privsep.c b/src/privsep.c index 76668fb2..316520c1 100644 --- a/src/privsep.c +++ b/src/privsep.c @@ -760,7 +760,7 @@ ps_stopwait(struct dhcpcd_ctx *ctx) #endif error = eloop_start(ctx->ps_eloop); - if (error != EXIT_SUCCESS) + if (error < 0) logerr("%s: eloop_start", __func__); eloop_timeout_delete(ctx->ps_eloop, ps_process_timeout, ctx);