From 3935e0762c37b0e9a5eb5a7705b75d35046b47b4 Mon Sep 17 00:00:00 2001 From: Yuefu Zhou 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 --- 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