[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