.\" 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
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
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;
+ 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;
+ 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, 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);
+
+ if (ifo->options & DHCPCD_STOPPING) {
+ dhcp_free(ifp);
+ dhcpcd_dropped(ifp);
+ } else
+ dhcp_close(ifp);
+}
+
void
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;
}
#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
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
#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
#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",
free(state->offer);
free(state->clientid);
free(state);
+ ifp->if_data[IF_DATA_DHCP] = NULL;
}
ctx = ifp->ctx;
struct dhcp6_state *state;
state = D6_STATE(ifp);
- if (state->state != DH6S_BOUND)
+ if (state->state != DH6S_BOUND) {
+ dhcp6_finishrelease(ifp);
return;
+ }
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)
+ if (dhcp6_makemessage(ifp) == -1) {
logerr("%s: %s", __func__, ifp->name);
- else {
- dhcp6_sendrelease(ifp);
+ /* not much we can do apart from finish now */
dhcp6_finishrelease(ifp);
- }
+ } else
+ dhcp6_sendrelease(ifp);
}
static int
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;
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 */
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 (ifp->options == NULL ||
+ !(ifp->options->options & DHCPCD_STOPPING) ||
+ 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
ifp->carrier = carrier;
ifp->flags = flags;
+ /*
+ * 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_EXITING)
+ return;
+
if (!if_is_link_up(ifp)) {
if (!ifp->active || (!was_link_up && !was_roaming))
return;
}
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__);
}
}
-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;
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
#ifdef USE_SIGNALS
#define sigmsg "received %s, %s"
-static volatile bool dhcpcd_exiting = false;
void
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;
- dhcpcd_exiting = true;
- if (!(ctx->options & DHCPCD_TEST))
- stop_all_interfaces(ctx, opts);
+ ctx->options |= DHCPCD_EXITING;
+ if (!(ctx->options & DHCPCD_TEST) &&
+ stop_all_interfaces(ctx, opts))
+ {
+ /* We stopped something, we will exit once that is done. */
+ eloop_exitallinners(exit_code);
+ return;
+ }
+
eloop_exitall(exit_code);
- dhcpcd_exiting = false;
}
#endif
if (opts & (DHCPCD_EXITING | DHCPCD_RELEASE)) {
if (oifind == argc && af == AF_UNSPEC) {
- stop_all_interfaces(ctx, opts);
- eloop_exit(ctx->eloop, EXIT_SUCCESS);
+ ctx->options |= DHCPCD_EXITING;
+ 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;
}
if (af != AF_UNSPEC)
dhcpcd_drop_af(ifp, 1, af);
else
- stop_interface(ifp, NULL);
+ stop_interface(ifp);
ifo->options = orig_opts;
}
return 0;
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);
+ 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)
{
int fd_null = open(_PATH_DEVNULL, O_WRONLY);
/* 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;
}
}
if (ctx.options & DHCPCD_STARTED && !(ctx.options & DHCPCD_FORKED))
loginfox(PACKAGE " exited");
#ifdef PRIVSEP
- if (ctx.ps_root != NULL && ps_root_stop(&ctx) == -1)
+ if (ps_root_stop(&ctx) == -1)
i = EXIT_FAILURE;
eloop_free(ctx.ps_eloop);
#endif
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 *);
#define KEVENT_N int
#endif
#elif defined(__linux__)
+#include <linux/version.h>
#include <sys/epoll.h>
+#include <poll.h>
#define USE_EPOLL
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
+#define HAVE_EPOLL_PWAIT2
+#endif
#else
#include <poll.h>
#define USE_PPOLL
bool exitnow;
bool events_need_setup;
bool events_invalid;
+#ifdef HAVE_EPOLL_PWAIT2
+ bool epoll_pwait2_nosys;
+#endif
};
TAILQ_HEAD(eloop_head, eloop) eloops = TAILQ_HEAD_INITIALIZER(eloops);
}
}
+void
+eloop_exitallinners(int code)
+{
+ struct eloop *eloop;
+
+ TAILQ_FOREACH(eloop, &eloops, next) {
+ if (eloop == TAILQ_FIRST(&eloops))
+ continue;
+ eloop->exitcode = code;
+ eloop->exitnow = true;
+ }
+}
+
#if defined(USE_KQUEUE) || defined(USE_EPOLL)
static int
eloop_open(struct eloop *eloop)
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);
+#ifdef HAVE_EPOLL_PWAIT2
+ } else if (!eloop->epoll_pwait2_nosys) {
+ /* Many linux distros are dumb in shipping newer
+ * kernel headers than the target kernel they are using.
+ * So we jump through this stupid hoop and have to write
+ * more complex code. */
+ n = epoll_pwait2(eloop->fd, eloop->fds, (int)eloop->nfds,
+ ts, &eloop->sigset);
+ if (n == -1 && errno == ENOSYS) {
+ eloop->epoll_pwait2_nosys = true;
+ goto epoll_pwait2_nosys;
+ }
+#endif
+ } else {
+ int timeout;
- n = epoll_pwait(eloop->fd, eloop->fds, (int)eloop->nfds, timeout,
- &eloop->sigset);
+#ifdef HAVE_EPOLL_PWAIT2
+epoll_pwait2_nosys:
+#endif
+ 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);
+ }
if (n == -1)
return -1;
void eloop_free(struct eloop *);
void eloop_exit(struct eloop *, int);
void eloop_exitall(int);
+void eloop_exitallinners(int);
int eloop_forked(struct eloop *, unsigned short);
int eloop_start(struct eloop *);
assert(ifp != NULL);
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
ipv4ll_freearp(ifp);
if ((ifp->options->options & DHCPCD_NODROP) == DHCPCD_NODROP)
- return;
+ goto free;
state = IPV4LL_STATE(ifp);
if (state) {
rt_build(ifp->ctx, AF_INET);
script_runreason(ifp, "IPV4LL");
}
+
+free:
+ ipv4ll_free(ifp);
+ dhcpcd_dropped(ifp);
}
void
/* 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;
#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
#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);
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);
}
if (stop) {
+stop:
ctx->options |= DHCPCD_EXITING;
#ifdef PRIVSEP_DEBUG
logdebugx("process %d stopping", getpid());
#endif
ps_free(ctx);
- eloop_exit(ctx->eloop, len != -1 ? EXIT_SUCCESS : EXIT_FAILURE);
+ eloop_exitall(len != -1 ? EXIT_SUCCESS : EXIT_FAILURE);
return len;
}
dlen -= sizeof(psm.psm_hdr);
}
#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