[Dnsmasq-discuss] [PATCH] Close listening socket on fatal accept() errors to prevent busy loop
Simon Kelley
simon at thekelleys.org.uk
Sun Sep 6 11:58:20 UTC 2026
Patch applied, thanks.
Simon.
On 20.08.2026 07:52, zhou yuefu wrote:
> Hi Simon,
>
> If a TCP listening socket is externally destroyed (e.g., via ss -K,
> or a process using NETLINK_SOCK_DIAG/SOCK_DESTROY), accept()
> permanently returns -1 with errno == EINVAL because the socket is no
> longer in TCP_LISTEN state. Since poll() keeps reporting the stale fd
> as readable, the main loop spins calling do_tcp_connection() ->
> accept() indefinitely, consuming 100% CPU.
>
> Reproduce:
>
> $ dnsmasq --bind-dynamic --interface=lo --port=15353 --no-daemon &
> $ sudo ss -K state listening sport = :15353
> $ # dnsmasq now spins at 100% CPU
>
> The fix distinguishes transient errors from fatal ones. On transient
> errors (EAGAIN, ECONNABORTED, EMFILE, ENFILE, ENOMEM, ENOBUFS), we
> just return and retry on the next poll cycle. On fatal errors (EINVAL,
> EBADF, etc.), we close the tcpfd and mark it -1, so poll() no longer
> selects it.
>
> In --bind-dynamic mode, the listener will be automatically rebuilt on
> the next address change event via newaddress(). Alternatively, a
> dedicated event could trigger targeted listener rebuild without the
> DHCPv6/RA side effects of EVENT_NEWADDR — happy to implement that if
> you prefer.
>
> Tested: dnsmasq 2.93 on Linux 6.1/5.15, verified 0% CPU after socket
> destruction with the patch (vs 100% without).
>
> diff --git a/src/dnsmasq.c b/src/dnsmasq.c
> index c1e48fc..fa4a467 100644
> --- a/src/dnsmasq.c
> +++ b/src/dnsmasq.c
> @@ -2029,7 +2029,20 @@ static void do_tcp_connection(struct listener
> *listener, time_t now, int slot)
> while ((confd = accept(listener->tcpfd, NULL, NULL)) == -1 &&
> errno == EINTR);
>
> if (confd == -1)
> - return;
> + {
> + /* Transient errors: just return and retry on next poll cycle. */
> + if (errno == EAGAIN || errno == ECONNABORTED ||
> + errno == EMFILE || errno == ENFILE ||
> + errno == ENOMEM || errno == ENOBUFS)
> + return;
> +
> + /* Fatal error (EINVAL, EBADF, etc): socket is permanently broken.
> + Close it so poll() no longer selects it. In --bind-dynamic
> mode
> + the listener will be rebuilt on the next address change
> event. */
> + close(listener->tcpfd);
> + listener->tcpfd = -1;
> + return;
> + }
>
> if (getsockname(confd, (struct sockaddr *)&tcp_addr, &tcp_len) == -1)
> {
>
> Signed-off-by: Yuefu Zhou yuefu16.zhou at gmail.com
> <mailto:yuefu16.zhou at gmail.com>
>
More information about the Dnsmasq-discuss
mailing list