]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
Protocols will notify when dhcpcd can exit
authorRoy Marples <roy@marples.name>
Mon, 6 Oct 2025 11:30:21 +0000 (12:30 +0100)
committerRoy Marples <roy@marples.name>
Mon, 3 Nov 2025 09:46:57 +0000 (09:46 +0000)
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
src/dhcp6.c
src/dhcpcd.c
src/dhcpcd.h
src/ipv4ll.c
src/ipv6nd.c
src/route.c

index c4de0e4f08dc7349e0bc537bb4d401394972ece0..d756082e5ae6084dc39f552da5f7795f73802473 100644 (file)
@@ -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;
index dbe73de21ee856e363d70236e077d230e8d3892c..4c4b7b5ae011fd8a75993037767ea50a1f5c6142 100644 (file)
@@ -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 */
index 5fdd7c1c400557bd0d4fde56db2572c7a50f3607..1f479b1d52f59a3c6680aa4e39f3b1c8510b6a5f 100644 (file)
@@ -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 @@ dumperr:
 
        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 @@ dumperr:
                        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 @@ printpidfile:
                        /* 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;
                }
        }
index 527399d344b3c7e364f4abcf0c7301d45c5abab4..c4dbe9993014152bb18cf7461f8053a92851ce58 100644 (file)
@@ -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 *);
index c2c7a8e7d3fcc2b526e09497860b418eb7058180..4dc3ba7c897ad5af66a1bfd985ec29fabeed5f88 100644 (file)
@@ -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
index 09e0d16ed2e7b1dd390f131a753fafcd54e17ad1..c8c18fc4fccdf7faf2c855b1a09ee2a1bd3b2aa1 100644 (file)
@@ -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;
 
index 32d132b1e331114868ac9a42bb5b999f7c471a73..7861546efdd9c7bdc1ef40cfdb13a385c10ae2b3 100644 (file)
@@ -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