From 093ff78e04931648474354157e40693d0fec82d4 Mon Sep 17 00:00:00 2001 From: Feng Wang Date: Thu, 20 Aug 2026 15:08:32 +0800 Subject: [PATCH 1/2] Add timeout for udp dns requests to avoid resource leak --- src/linux/init/DnsServer.cpp | 48 +++++++++++++++++++++++++++++++----- src/linux/init/DnsServer.h | 17 ++++++++++++- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/linux/init/DnsServer.cpp b/src/linux/init/DnsServer.cpp index dcad653498..79be68878e 100644 --- a/src/linux/init/DnsServer.cpp +++ b/src/linux/init/DnsServer.cpp @@ -5,7 +5,6 @@ #include #include #include "DnsServer.h" -#include "RuntimeErrorWithSourceLocation.h" #include "Syscall.h" #include "util.h" @@ -15,6 +14,8 @@ constexpr int c_dnsServerPort = 53; constexpr int c_epollWaitMaxEvents = 100; // Maximum size of DNS over UDP requests is 4096 bytes (max size is reached for EDNS UDP requests) constexpr int c_maxUdpDnsBufferSize = 4096; +// Maximum time to wait for a tunneled UDP DNS response +constexpr auto c_udpRequestTimeout = std::chrono::seconds{60}; // Max number of pending connections in the TCP listen queue constexpr int c_maxListenBacklog = 1000; @@ -119,9 +120,12 @@ try } // Stop tracking the request, irrespective of the DNS response being successfully sent - const auto removeDnsRequest = wil::scope_exit([&] { m_udpRequests.erase(dnsClientIdentifier.DnsClientId); }); + const auto removeDnsRequest = wil::scope_exit([&] { + m_udpRequestExpirations.erase(it->second.m_expiration); + m_udpRequests.erase(it); + }); - sockaddr_in& remoteAddr = it->second; + sockaddr_in& remoteAddr = it->second.m_remoteAddress; // Send DNS response buffer back to the Linux DNS client int bufferSize = dnsBuffer.size(); @@ -298,6 +302,25 @@ try } CATCH_LOG() +int DnsServer::ExpireUdpRequestsAndGetTimeout() noexcept +{ + std::scoped_lock lock{m_udpLock}; + const auto now = std::chrono::steady_clock::now(); + + while (!m_udpRequestExpirations.empty() && m_udpRequestExpirations.front().first <= now) + { + m_udpRequests.erase(m_udpRequestExpirations.front().second); + m_udpRequestExpirations.pop_front(); + } + + if (m_udpRequestExpirations.empty()) + { + return -1; + } + + return static_cast(std::chrono::ceil(m_udpRequestExpirations.front().first - now).count()); +} + void DnsServer::ServerLoop() noexcept { UtilSetThreadName("DnsServer"); @@ -311,7 +334,8 @@ void DnsServer::ServerLoop() noexcept { // A fixed number of events is requested from epoll_wait (c_epollWaitMaxEvents). In case the number of ready events is // greater than c_epollWaitMaxEvents, epoll will round-robin through the ready events until we get a notification for all of them. - size_t numReadyEvents = Syscall(epoll_wait, m_epollFd.get(), events, c_epollWaitMaxEvents, -1); + const auto timeout = ExpireUdpRequestsAndGetTimeout(); + size_t numReadyEvents = Syscall(epoll_wait, m_epollFd.get(), events, c_epollWaitMaxEvents, timeout); // No event if (numReadyEvents == 0) @@ -388,14 +412,26 @@ try udpRequestId = requestId; // Track the request - m_udpRequests.emplace(requestId, remoteAddr); + const auto expiration = std::chrono::steady_clock::now() + c_udpRequestTimeout; + const auto expirationIt = m_udpRequestExpirations.emplace(m_udpRequestExpirations.end(), expiration, requestId); + auto removeExpirationOnError = wil::scope_exit([&] { m_udpRequestExpirations.erase(expirationIt); }); + + const auto [_, inserted] = m_udpRequests.emplace(requestId, UdpRequestContext{remoteAddr, expirationIt}); + THROW_UNEXPECTED_IF(!inserted); + + removeExpirationOnError.release(); } if (!dnsRequest.empty()) { auto removeRequestOnError = wil::scope_exit([&] { std::scoped_lock lock{m_udpLock}; - m_udpRequests.erase(udpRequestId); + const auto it = m_udpRequests.find(udpRequestId); + if (it != m_udpRequests.end()) + { + m_udpRequestExpirations.erase(it->second.m_expiration); + m_udpRequests.erase(it); + } }); // Tunnel request to Windows diff --git a/src/linux/init/DnsServer.h b/src/linux/init/DnsServer.h index d4729bfbb7..7f57228c15 100644 --- a/src/linux/init/DnsServer.h +++ b/src/linux/init/DnsServer.h @@ -2,6 +2,8 @@ #pragma once +#include +#include #include #include "common.h" #include "lxinitshared.h" @@ -68,6 +70,14 @@ class DnsServer TcpConnectionContext& operator=(TcpConnectionContext&&) = delete; }; + using UdpRequestExpirationQueue = std::list>; + + struct UdpRequestContext + { + sockaddr_in m_remoteAddress; + UdpRequestExpirationQueue::iterator m_expiration; + }; + void StartUdpDnsServer(const std::string& ipAddress) noexcept; void StartTcpDnsServer(const std::string& ipAddress) noexcept; @@ -84,6 +94,8 @@ class DnsServer // Read the next DNS request from the UDP socket. void HandleUdpDnsRequest() noexcept; + int ExpireUdpRequestsAndGetTimeout() noexcept; + void HandleUdpDnsResponse(const gsl::span dnsBuffer, const LX_GNS_DNS_CLIENT_IDENTIFIER& dnsClientIdentifier) noexcept; void HandleTcpDnsResponse(const gsl::span dnsBuffer, const LX_GNS_DNS_CLIENT_IDENTIFIER& dnsClientIdentifier) noexcept; @@ -105,7 +117,10 @@ class DnsServer // Mapping id of an UDP DNS request to the sockaddr_in struct storing the IP and port used by the Linux DNS client that made // the DNS request. Note: Since we only configure an IPv4 DNS server in Linux, we expect all Linux DNS clients to use IPv4 // addresses. _Guarded_by_(m_udpLock) - std::map m_udpRequests; + std::map m_udpRequests; + + // UDP requests ordered by expiration time. _Guarded_by_(m_udpLock) + UdpRequestExpirationQueue m_udpRequestExpirations; wil::unique_fd m_tcpListenSocket; From ca0e98fd6df0aea162cfc93d14c9bd63c4889c03 Mon Sep 17 00:00:00 2001 From: Feng Wang Date: Thu, 20 Aug 2026 15:18:49 +0800 Subject: [PATCH 2/2] revert unrelated change --- src/linux/init/DnsServer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/linux/init/DnsServer.cpp b/src/linux/init/DnsServer.cpp index 79be68878e..680f537cf8 100644 --- a/src/linux/init/DnsServer.cpp +++ b/src/linux/init/DnsServer.cpp @@ -4,6 +4,7 @@ #include #include #include +#include "RuntimeErrorWithSourceLocation.h" #include "DnsServer.h" #include "Syscall.h" #include "util.h"