[Dnsmasq-discuss] [PATCH] Close listening socket on fatal accept() errors to prevent busy loop
zhou yuefu
yuefu16.zhou at gmail.com
Thu Aug 20 06:52:19 UTC 2026
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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/attachments/20260820/78c06432/attachment.htm>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0001-Close-listening-socket-on-fatal-accept-errors-to-pre.patch
Type: text/x-patch
Size: 2113 bytes
Desc: not available
URL: <http://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/attachments/20260820/78c06432/attachment.bin>
More information about the Dnsmasq-discuss
mailing list