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 c4de0e4f..89be71a5 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -2866,6 +2866,51 @@ 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; + 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) { @@ -2876,6 +2921,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 +2932,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 +2946,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 +2977,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 +3137,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 +3967,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..2cca6e6d 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -2110,29 +2110,25 @@ 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; + } 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 @@ -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 2b84ca01..4881f7dd 100644 --- a/src/dhcpcd.c +++ b/src/dhcpcd.c @@ -438,27 +438,62 @@ 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 (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 @@ -744,6 +779,17 @@ dhcpcd_handlecarrier(struct interface *ifp, int carrier, unsigned int flags) 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; @@ -1070,13 +1116,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__); @@ -1378,14 +1427,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 +1448,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 +1489,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 +1568,19 @@ 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 @@ -1664,8 +1720,11 @@ 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); + 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; } @@ -1695,7 +1754,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 +1956,28 @@ 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); + 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); @@ -2271,6 +2341,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; } } @@ -2723,7 +2795,7 @@ main(int argc, char **argv, char **envp) 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 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/eloop.c b/src/eloop.c index b2d2eed1..ac6db771 100644 --- a/src/eloop.c +++ b/src/eloop.c @@ -46,8 +46,13 @@ #define KEVENT_N int #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 @@ -143,6 +148,9 @@ struct eloop { 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); @@ -617,6 +625,19 @@ eloop_exitall(int code) } } +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) @@ -924,24 +945,48 @@ 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); +#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; diff --git a/src/eloop.h b/src/eloop.h index b342c37d..f498ddac 100644 --- a/src/eloop.h +++ b/src/eloop.h @@ -98,6 +98,7 @@ struct eloop *eloop_new_with_signals(struct eloop *); 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 *); diff --git a/src/ipv4ll.c b/src/ipv4ll.c index c2c7a8e7..c16a1dca 100644 --- a/src/ipv4ll.c +++ b/src/ipv4ll.c @@ -448,10 +448,11 @@ 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) - return; + goto free; state = IPV4LL_STATE(ifp); if (state) { @@ -481,6 +482,10 @@ ipv4ll_drop(struct interface *ifp) rt_build(ifp->ctx, AF_INET); script_runreason(ifp, "IPV4LL"); } + +free: + 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/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 diff --git a/src/privsep.c b/src/privsep.c index 1fff0798..e7f488ed 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); @@ -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,12 +1167,13 @@ 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()); #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); 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