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 [email protected]
From 3935e0762c37b0e9a5eb5a7705b75d35046b47b4 Mon Sep 17 00:00:00 2001
From: Yuefu Zhou <[email protected]>
Date: Thu, 20 Aug 2026 14:39:41 +0800
Subject: [PATCH] Close listening socket on fatal accept() errors to prevent
busy loop
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.
Distinguish transient errors (EAGAIN, ECONNABORTED, EMFILE, ENFILE,
ENOMEM, ENOBUFS) from fatal ones. On transient errors just return
and retry on the next poll cycle. On fatal errors 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().
Signed-off-by: Yuefu Zhou <[email protected]>
---
src/dnsmasq.c | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
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)
{
--
2.25.1
_______________________________________________
Dnsmasq-discuss mailing list
[email protected]
https://lists.thekelleys.org.uk/cgi-bin/mailman/listinfo/dnsmasq-discuss