Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package dnsdist for openSUSE:Factory checked in at 2025-05-26 18:40:36 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/dnsdist (Old) and /work/SRC/openSUSE:Factory/.dnsdist.new.2732 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "dnsdist" Mon May 26 18:40:36 2025 rev:13 rq:1280131 version:1.9.10 Changes: -------- --- /work/SRC/openSUSE:Factory/dnsdist/dnsdist.changes 2024-11-05 15:42:02.844656439 +0100 +++ /work/SRC/openSUSE:Factory/.dnsdist.new.2732/dnsdist.changes 2025-05-26 18:41:48.859241858 +0200 @@ -1,0 +2,6 @@ +Mon May 26 11:13:10 UTC 2025 - Adam Majer <adam.ma...@suse.de> + +- update to 1.9.10: (bsc#1243378, bsc#1242028, CVE-2025-30194, CVE-2025-30193) + https://www.dnsdist.org/changelog.html#change-1.9.10 + +------------------------------------------------------------------- Old: ---- dnsdist-1.9.7.tar.bz2 dnsdist-1.9.7.tar.bz2.sig New: ---- dnsdist-1.9.10.tar.bz2 dnsdist-1.9.10.tar.bz2.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ dnsdist.spec ++++++ --- /var/tmp/diff_new_pack.ZivKKn/_old 2025-05-26 18:41:49.299260342 +0200 +++ /var/tmp/diff_new_pack.ZivKKn/_new 2025-05-26 18:41:49.299260342 +0200 @@ -1,7 +1,7 @@ # # spec file for package dnsdist # -# Copyright (c) 2024 SUSE LLC +# Copyright (c) 2025 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -38,7 +38,7 @@ %bcond_with dnsdist_luajit %endif Name: dnsdist -Version: 1.9.7 +Version: 1.9.10 Release: 0 Summary: A highly DNS-, DoS- and abuse-aware loadbalancer License: GPL-2.0-only ++++++ dnsdist-1.9.7.tar.bz2 -> dnsdist-1.9.10.tar.bz2 ++++++ ++++ 1924 lines of diff (skipped) ++++ retrying with extended exclude list diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/config.h.in new/dnsdist-1.9.10/config.h.in --- old/dnsdist-1.9.7/config.h.in 2024-10-03 17:33:15.000000000 +0200 +++ new/dnsdist-1.9.10/config.h.in 2025-05-20 11:13:46.000000000 +0200 @@ -243,6 +243,10 @@ /* Define to 1 if you have quiche */ #undef HAVE_QUICHE +/* Define to 1 if the Quiche API has quiche_h3_event_headers_has_more_frames + instead of quiche_h3_event_headers_has_body */ +#undef HAVE_QUICHE_H3_EVENT_HEADERS_HAS_MORE_FRAMES + /* Define to 1 if the Quiche API includes error code in quiche_conn_stream_recv and quiche_conn_stream_send */ #undef HAVE_QUICHE_STREAM_ERROR_CODES diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/configure.ac new/dnsdist-1.9.10/configure.ac --- old/dnsdist-1.9.7/configure.ac 2024-10-03 17:33:05.000000000 +0200 +++ new/dnsdist-1.9.10/configure.ac 2025-05-20 11:13:35.000000000 +0200 @@ -1,6 +1,6 @@ AC_PREREQ([2.69]) -AC_INIT([dnsdist], [1.9.7]) +AC_INIT([dnsdist], [1.9.10]) AM_INIT_AUTOMAKE([foreign tar-ustar dist-bzip2 no-dist-gzip parallel-tests 1.11 subdir-objects]) AM_SILENT_RULES([yes]) AC_CONFIG_MACRO_DIR([m4]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/credentials.hh new/dnsdist-1.9.10/credentials.hh --- old/dnsdist-1.9.7/credentials.hh 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/credentials.hh 2025-05-20 11:13:25.000000000 +0200 @@ -21,7 +21,7 @@ */ #pragma once -#include <memory> +#include <cstdint> #include <string> class SensitiveData diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist-backend.cc new/dnsdist-1.9.10/dnsdist-backend.cc --- old/dnsdist-1.9.7/dnsdist-backend.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist-backend.cc 2025-05-20 11:13:25.000000000 +0200 @@ -144,7 +144,12 @@ } catch (const std::runtime_error& error) { if (initialAttempt || g_verbose) { - infolog("Error connecting to new server with address %s: %s", d_config.remote.toStringWithPort(), error.what()); + if (!IsAnyAddress(d_config.sourceAddr) || !d_config.sourceItfName.empty()) { + infolog("Error connecting to new server with address %s (source address: %s, source interface: %s): %s", d_config.remote.toStringWithPort(), IsAnyAddress(d_config.sourceAddr) ? "not set" : d_config.sourceAddr.toString(), d_config.sourceItfName.empty() ? "not set" : d_config.sourceItfName, error.what()); + } + else { + infolog("Error connecting to new server with address %s: %s", d_config.remote.toStringWithPort(), error.what()); + } } connected = false; break; @@ -974,6 +979,13 @@ serv.first = idx++; } *servers = std::make_shared<const ServerPolicy::NumberedServerVector>(std::move(newServers)); + + if ((*servers)->size() == 1) { + d_tcpOnly = server->isTCPOnly(); + } + else if (!server->isTCPOnly()) { + d_tcpOnly = false; + } } void ServerPool::removeServer(shared_ptr<DownstreamState>& server) @@ -984,8 +996,10 @@ auto newServers = std::make_shared<ServerPolicy::NumberedServerVector>(*(*servers)); size_t idx = 1; bool found = false; + bool tcpOnly = true; for (auto it = newServers->begin(); it != newServers->end();) { if (found) { + tcpOnly = tcpOnly && it->second->isTCPOnly(); /* we need to renumber the servers placed after the removed one, for Lua (custom policies) */ it->first = idx++; @@ -995,9 +1009,11 @@ it = newServers->erase(it); found = true; } else { + tcpOnly = tcpOnly && it->second->isTCPOnly(); idx++; it++; } } + d_tcpOnly = tcpOnly; *servers = std::move(newServers); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist-doh-common.cc new/dnsdist-1.9.10/dnsdist-doh-common.cc --- old/dnsdist-1.9.7/dnsdist-doh-common.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist-doh-common.cc 2025-05-20 11:13:25.000000000 +0200 @@ -99,6 +99,11 @@ return d_tlsContext.loadTicketsKeys(keyFile); } +void DOHFrontend::loadTicketsKey(const std::string& key) +{ + return d_tlsContext.loadTicketsKey(key); +} + void DOHFrontend::handleTicketsKeyRotation() { } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist-doh-common.hh new/dnsdist-1.9.10/dnsdist-doh-common.hh --- old/dnsdist-1.9.7/dnsdist-doh-common.hh 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist-doh-common.hh 2025-05-20 11:13:25.000000000 +0200 @@ -166,6 +166,10 @@ { } + virtual void loadTicketsKey(const std::string& /* key */) + { + } + virtual void handleTicketsKeyRotation() { } @@ -187,6 +191,7 @@ virtual void rotateTicketsKey(time_t now); virtual void loadTicketsKeys(const std::string& keyFile); + virtual void loadTicketsKey(const std::string& key); virtual void handleTicketsKeyRotation(); virtual std::string getNextTicketsKeyRotation() const; virtual size_t getTicketsKeysCount(); @@ -230,16 +235,16 @@ static void handleTimeout(std::unique_ptr<DOHUnitInterface> unit) { if (unit) { - unit->handleTimeout(); - unit.release(); + auto* ptr = unit.release(); + ptr->handleTimeout(); } } static void handleUDPResponse(std::unique_ptr<DOHUnitInterface> unit, PacketBuffer&& response, InternalQueryState&& state, const std::shared_ptr<DownstreamState>& ds) { if (unit) { - unit->handleUDPResponse(std::move(response), std::move(state), ds); - unit.release(); + auto* ptr = unit.release(); + ptr->handleUDPResponse(std::move(response), std::move(state), ds); } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist-idstate.hh new/dnsdist-1.9.10/dnsdist-idstate.hh --- old/dnsdist-1.9.7/dnsdist-idstate.hh 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist-idstate.hh 2025-05-20 11:13:25.000000000 +0200 @@ -155,8 +155,8 @@ int32_t d_streamID{-1}; // 4 uint32_t cacheKey{0}; // 4 uint32_t cacheKeyNoECS{0}; // 4 - // DoH-only */ - uint32_t cacheKeyUDP{0}; // 4 + // DoH-only: if we received a TC=1 answer, we had to retry over TCP and thus we need the TCP cache key */ + uint32_t cacheKeyTCP{0}; // 4 uint32_t ttlCap{0}; // cap the TTL _after_ inserting into the packet cache // 4 int backendFD{-1}; // 4 int delayMsec{0}; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist-lua-bindings-dnsquestion.cc new/dnsdist-1.9.10/dnsdist-lua-bindings-dnsquestion.cc --- old/dnsdist-1.9.7/dnsdist-lua-bindings-dnsquestion.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist-lua-bindings-dnsquestion.cc 2025-05-20 11:13:25.000000000 +0200 @@ -138,6 +138,13 @@ return dq.sni; }); + luaCtx.registerFunction<std::string (DNSQuestion::*)() const>("getIncomingInterface", [](const DNSQuestion& dnsQuestion) -> std::string { + if (dnsQuestion.ids.cs != nullptr) { + return dnsQuestion.ids.cs->interface; + } + return {}; + }); + luaCtx.registerFunction<std::string (DNSQuestion::*)()const>("getProtocol", [](const DNSQuestion& dq) { return dq.getProtocol().toPrettyString(); }); @@ -146,6 +153,10 @@ return dq.ids.queryRealTime.getStartTime(); }); + luaCtx.registerFunction<double (DNSQuestion::*)() const>("getElapsedUs", [](const DNSQuestion& dnsQuestion) { + return dnsQuestion.ids.queryRealTime.udiff(); + }); + luaCtx.registerFunction<void(DNSQuestion::*)(std::string)>("sendTrap", [](const DNSQuestion& dq, boost::optional<std::string> reason) { #ifdef HAVE_NET_SNMP if (g_snmpAgent && g_snmpTrapsEnabled) { @@ -450,6 +461,17 @@ return dr.ids.queryRealTime.getStartTime(); }); + luaCtx.registerFunction<double (DNSResponse::*)() const>("getElapsedUs", [](const DNSResponse& dnsResponse) { + return dnsResponse.ids.queryRealTime.udiff(); + }); + + luaCtx.registerFunction<std::string (DNSResponse::*)() const>("getIncomingInterface", [](const DNSResponse& dnsResponse) -> std::string { + if (dnsResponse.ids.cs != nullptr) { + return dnsResponse.ids.cs->interface; + } + return {}; + }); + luaCtx.registerFunction<void(DNSResponse::*)(std::string)>("sendTrap", [](const DNSResponse& dr, boost::optional<std::string> reason) { #ifdef HAVE_NET_SNMP if (g_snmpAgent && g_snmpTrapsEnabled) { @@ -543,6 +565,7 @@ } dr.asynchronous = true; dr.getMutableData() = *dr.ids.d_packet; + dr.ids.d_proxyProtocolPayloadSize = 0; auto query = dnsdist::getInternalQueryFromDQ(dr, false); return dnsdist::queueQueryResumptionEvent(std::move(query)); }); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist-lua-bindings.cc new/dnsdist-1.9.10/dnsdist-lua-bindings.cc --- old/dnsdist-1.9.7/dnsdist-lua-bindings.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist-lua-bindings.cc 2025-05-20 11:13:25.000000000 +0200 @@ -845,7 +845,7 @@ if (client || configCheck) { return; } - std::thread newThread(dnsdist::resolver::asynchronousResolver, std::move(hostname), [callback=std::move(callback)](const std::string& resolvedHostname, std::vector<ComboAddress>& ips) { + std::thread newThread(dnsdist::resolver::asynchronousResolver, std::move(hostname), [callback = std::move(callback)](const std::string& resolvedHostname, std::vector<ComboAddress>& ips) mutable { LuaArray<ComboAddress> result; result.reserve(ips.size()); for (const auto& entry : ips) { @@ -853,7 +853,15 @@ } { auto lua = g_lua.lock(); - callback(resolvedHostname, result); + try { + callback(resolvedHostname, result); + } + catch (const std::exception& exp) { + vinfolog("Error during execution of getAddressInfo callback: %s", exp.what()); + } + // this _needs_ to be done while we are holding the lock, + // otherwise the destructor will corrupt the stack + callback = nullptr; dnsdist::handleQueuedAsynchronousEvents(); } }); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist-lua-ffi-interface.h new/dnsdist-1.9.10/dnsdist-lua-ffi-interface.h --- old/dnsdist-1.9.7/dnsdist-lua-ffi-interface.h 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist-lua-ffi-interface.h 2025-05-20 11:13:25.000000000 +0200 @@ -64,6 +64,7 @@ void dnsdist_ffi_dnsquestion_get_remoteaddr(const dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize) __attribute__ ((visibility ("default"))); void dnsdist_ffi_dnsquestion_get_masked_remoteaddr(dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize, uint8_t bits) __attribute__ ((visibility ("default"))); uint16_t dnsdist_ffi_dnsquestion_get_remote_port(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default"))); +const char* dnsdist_ffi_dnsquestion_get_incoming_interface(const dnsdist_ffi_dnsquestion_t* dnsQuestion) __attribute__ ((visibility ("default"))); void dnsdist_ffi_dnsquestion_get_qname_raw(const dnsdist_ffi_dnsquestion_t* dq, const char** qname, size_t* qnameSize) __attribute__ ((visibility ("default"))); size_t dnsdist_ffi_dnsquestion_get_qname_hash(const dnsdist_ffi_dnsquestion_t* dq, size_t init) __attribute__ ((visibility ("default"))); uint16_t dnsdist_ffi_dnsquestion_get_qtype(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default"))); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist-lua-ffi-interface.inc new/dnsdist-1.9.10/dnsdist-lua-ffi-interface.inc --- old/dnsdist-1.9.7/dnsdist-lua-ffi-interface.inc 2024-10-03 17:33:23.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist-lua-ffi-interface.inc 2025-05-20 11:13:53.000000000 +0200 @@ -65,6 +65,7 @@ void dnsdist_ffi_dnsquestion_get_remoteaddr(const dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize) __attribute__ ((visibility ("default"))); void dnsdist_ffi_dnsquestion_get_masked_remoteaddr(dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize, uint8_t bits) __attribute__ ((visibility ("default"))); uint16_t dnsdist_ffi_dnsquestion_get_remote_port(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default"))); +const char* dnsdist_ffi_dnsquestion_get_incoming_interface(const dnsdist_ffi_dnsquestion_t* dnsQuestion) __attribute__ ((visibility ("default"))); void dnsdist_ffi_dnsquestion_get_qname_raw(const dnsdist_ffi_dnsquestion_t* dq, const char** qname, size_t* qnameSize) __attribute__ ((visibility ("default"))); size_t dnsdist_ffi_dnsquestion_get_qname_hash(const dnsdist_ffi_dnsquestion_t* dq, size_t init) __attribute__ ((visibility ("default"))); uint16_t dnsdist_ffi_dnsquestion_get_qtype(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default"))); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist-lua-ffi.cc new/dnsdist-1.9.10/dnsdist-lua-ffi.cc --- old/dnsdist-1.9.7/dnsdist-lua-ffi.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist-lua-ffi.cc 2025-05-20 11:13:25.000000000 +0200 @@ -121,6 +121,14 @@ return dq->dq->ids.origRemote.getPort(); } +const char* dnsdist_ffi_dnsquestion_get_incoming_interface(const dnsdist_ffi_dnsquestion_t* dnsQuestion) +{ + if (dnsQuestion == nullptr || dnsQuestion->dq == nullptr || dnsQuestion->dq->ids.cs == nullptr) { + return nullptr; + } + return dnsQuestion->dq->ids.cs->interface.c_str(); +} + void dnsdist_ffi_dnsquestion_get_qname_raw(const dnsdist_ffi_dnsquestion_t* dq, const char** qname, size_t* qnameSize) { const auto& storage = dq->dq->ids.qname.getStorage(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist-lua-hooks.cc new/dnsdist-1.9.10/dnsdist-lua-hooks.cc --- old/dnsdist-1.9.7/dnsdist-lua-hooks.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist-lua-hooks.cc 2025-05-20 11:13:25.000000000 +0200 @@ -7,7 +7,7 @@ namespace dnsdist::lua::hooks { using MaintenanceCallback = std::function<void()>; -using TicketsKeyAddedHook = std::function<void(const char*, size_t)>; +using TicketsKeyAddedHook = std::function<void(const std::string&, size_t)>; static LockGuarded<std::vector<MaintenanceCallback>> s_maintenanceHooks; @@ -35,7 +35,7 @@ TLSCtx::setTicketsKeyAddedHook([hook](const std::string& key) { try { auto lua = g_lua.lock(); - hook(key.c_str(), key.size()); + hook(key, key.size()); } catch (const std::exception& exp) { warnlog("Error calling the Lua hook after new tickets key has been added: %s", exp.what()); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist-lua.cc new/dnsdist-1.9.10/dnsdist-lua.cc --- old/dnsdist-1.9.7/dnsdist-lua.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist-lua.cc 2025-05-20 11:13:25.000000000 +0200 @@ -642,7 +642,7 @@ auto ret = std::make_shared<DownstreamState>(std::move(config), std::move(tlsCtx), !(client || configCheck)); #ifdef HAVE_XSK LuaArray<std::shared_ptr<XskSocket>> luaXskSockets; - if (getOptionalValue<LuaArray<std::shared_ptr<XskSocket>>>(vars, "xskSockets", luaXskSockets) > 0 && !luaXskSockets.empty()) { + if (!client && !configCheck && getOptionalValue<LuaArray<std::shared_ptr<XskSocket>>>(vars, "xskSockets", luaXskSockets) > 0 && !luaXskSockets.empty()) { if (g_configurationDone) { throw std::runtime_error("Adding a server with xsk at runtime is not supported"); } @@ -668,6 +668,13 @@ else if (!(client || configCheck)) { infolog("Added downstream server %s", ret->d_config.remote.toStringWithPort()); } + + if (client || configCheck) { + /* consume these in client or configuration check mode, to prevent warnings */ + std::string mac; + getOptionalValue<std::string>(vars, "MACAddr", mac); + getOptionalValue<LuaArray<std::shared_ptr<XskSocket>>>(vars, "xskSockets", luaXskSockets); + } #else /* HAVE_XSK */ if (!(client || configCheck)) { infolog("Added downstream server %s", ret->d_config.remote.toStringWithPort()); @@ -2126,7 +2133,7 @@ luaCtx.writeFunction("setConsistentHashingBalancingFactor", [](double factor) { setLuaSideEffect(); - if (factor >= 1.0) { + if (factor >= 1.0 || factor == 0) { g_consistentHashBalancingFactor = factor; } else { @@ -2138,7 +2145,7 @@ luaCtx.writeFunction("setWeightedBalancingFactor", [](double factor) { setLuaSideEffect(); - if (factor >= 1.0) { + if (factor >= 1.0 || factor == 0) { g_weightedBalancingFactor = factor; } else { @@ -3036,7 +3043,7 @@ } }); - luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)(boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>> certFiles, boost::variant<std::string, LuaArray<std::string>> keyFiles)>("loadNewCertificatesAndKeys", [](std::shared_ptr<DOHFrontend> frontend, boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>> certFiles, boost::variant<std::string, LuaArray<std::string>> keyFiles) { + luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)(boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>> certFiles, boost::variant<std::string, LuaArray<std::string>> keyFiles)>("loadNewCertificatesAndKeys", [](const std::shared_ptr<DOHFrontend>& frontend, const boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>>& certFiles, const boost::variant<std::string, LuaArray<std::string>>& keyFiles) { #ifdef HAVE_DNS_OVER_HTTPS if (frontend != nullptr) { if (loadTLSCertificateAndKeys("DOHFrontend::loadNewCertificatesAndKeys", frontend->d_tlsContext.d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) { @@ -3046,18 +3053,47 @@ #endif }); - luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)()>("rotateTicketsKey", [](std::shared_ptr<DOHFrontend> frontend) { + luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)()>("rotateTicketsKey", [](const std::shared_ptr<DOHFrontend>& frontend) { if (frontend != nullptr) { frontend->rotateTicketsKey(time(nullptr)); } }); - luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr<DOHFrontend> frontend, const std::string& file) { + luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)(const std::string&)>("loadTicketsKeys", [](const std::shared_ptr<DOHFrontend>& frontend, const std::string& file) { if (frontend != nullptr) { frontend->loadTicketsKeys(file); } }); + luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)(const std::string&)>("loadTicketsKey", [](const std::shared_ptr<DOHFrontend>& frontend, const std::string& key) { + if (frontend != nullptr) { + frontend->loadTicketsKey(key); + } + }); + + luaCtx.writeFunction("loadTicketsKey", [](const std::string& key) { + for (const auto& frontend : g_frontends) { + if (!frontend) { + continue; + } + try { +#ifdef HAVE_DNS_OVER_TLS + if (frontend->tlsFrontend) { + frontend->tlsFrontend->loadTicketsKey(key); + } +#endif /* HAVE_DNS_OVER_TLS */ +#ifdef HAVE_DNS_OVER_HTTPS + if (frontend->dohFrontend) { + frontend->dohFrontend->loadTicketsKey(key); + } +#endif /* HAVE_DNS_OVER_HTTPS */ + } + catch (const std::exception& e) { + errlog("Error loading given tickets key for local %s", frontend->local.toStringWithPort()); + } + } + }); + luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)(const LuaArray<std::shared_ptr<DOHResponseMapEntry>>&)>("setResponsesMap", [](std::shared_ptr<DOHFrontend> frontend, const LuaArray<std::shared_ptr<DOHResponseMapEntry>>& map) { if (frontend != nullptr) { auto newMap = std::make_shared<std::vector<std::shared_ptr<DOHResponseMapEntry>>>(); @@ -3288,6 +3324,16 @@ } }); + luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)(const std::string&)>("loadTicketsKey", [](std::shared_ptr<TLSFrontend>& frontend, const std::string& key) { + if (frontend == nullptr) { + return; + } + auto ctx = frontend->getContext(); + if (ctx) { + ctx->loadTicketsKey(key); + } + }); + luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)()>("reloadCertificates", [](const std::shared_ptr<TLSFrontend>& frontend) { if (frontend == nullptr) { return; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist-metrics.cc new/dnsdist-1.9.10/dnsdist-metrics.cc --- old/dnsdist-1.9.7/dnsdist-metrics.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist-metrics.cc 2025-05-20 11:13:25.000000000 +0200 @@ -203,7 +203,7 @@ metric->second.d_value += step; return metric->second.d_value.load(); } - return std::string("Unable to increment custom metric '") + std::string(name) + "': no such metric"; + return std::string("Unable to increment custom metric '") + std::string(name) + "': no such counter"; } std::variant<uint64_t, Error> decrementCustomCounter(const std::string_view& name, uint64_t step) @@ -214,7 +214,7 @@ metric->second.d_value -= step; return metric->second.d_value.load(); } - return std::string("Unable to decrement custom metric '") + std::string(name) + "': no such metric"; + return std::string("Unable to decrement custom metric '") + std::string(name) + "': no such counter"; } std::variant<double, Error> setCustomGauge(const std::string_view& name, const double value) @@ -226,7 +226,7 @@ return value; } - return std::string("Unable to set metric '") + std::string(name) + "': no such metric"; + return std::string("Unable to set metric '") + std::string(name) + "': no such gauge"; } std::variant<double, Error> getCustomMetric(const std::string_view& name) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist-nghttp2-in.cc new/dnsdist-1.9.10/dnsdist-nghttp2-in.cc --- old/dnsdist-1.9.7/dnsdist-nghttp2-in.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist-nghttp2-in.cc 2025-05-20 11:13:25.000000000 +0200 @@ -552,7 +552,12 @@ if (response.d_idstate.d_streamID == -1) { throw std::runtime_error("Invalid DoH stream ID while sending response"); } - auto& context = d_currentStreams.at(response.d_idstate.d_streamID); + auto streamIt = d_currentStreams.find(response.d_idstate.d_streamID); + if (streamIt == d_currentStreams.end()) { + /* it might have been closed by the remote end in the meantime */ + return hasPendingWrite() ? IOState::NeedWrite : IOState::Done; + } + auto& context = streamIt->second; uint32_t statusCode = 200U; std::string contentType; @@ -587,7 +592,12 @@ throw std::runtime_error("Invalid DoH stream ID while handling I/O error notification"); } - auto& context = d_currentStreams.at(response.d_idstate.d_streamID); + auto streamIt = d_currentStreams.find(response.d_idstate.d_streamID); + if (streamIt == d_currentStreams.end()) { + /* it might have been closed by the remote end in the meantime */ + return; + } + auto& context = streamIt->second; context.d_buffer = std::move(response.d_buffer); sendResponse(response.d_idstate.d_streamID, context, 502, d_ci.cs->dohFrontend->d_customResponseHeaders); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist-proxy-protocol.cc new/dnsdist-1.9.10/dnsdist-proxy-protocol.cc --- old/dnsdist-1.9.7/dnsdist-proxy-protocol.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist-proxy-protocol.cc 2025-05-20 11:13:25.000000000 +0200 @@ -42,14 +42,19 @@ return addProxyProtocol(dq.getMutableData(), payload); } -bool addProxyProtocol(DNSQuestion& dq, size_t* payloadSize) +bool addProxyProtocol(DNSQuestion& dnsQuestion, size_t* proxyProtocolPayloadSize) { - auto payload = getProxyProtocolPayload(dq); - if (payloadSize != nullptr) { - *payloadSize = payload.size(); + auto payload = getProxyProtocolPayload(dnsQuestion); + size_t payloadSize = payload.size(); + + if (!addProxyProtocol(dnsQuestion, payload)) { + return false; } - return addProxyProtocol(dq, payload); + if (proxyProtocolPayloadSize != nullptr) { + *proxyProtocolPayloadSize = payloadSize; + } + return true; } bool addProxyProtocol(PacketBuffer& buffer, const std::string& payload) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist-proxy-protocol.hh new/dnsdist-1.9.10/dnsdist-proxy-protocol.hh --- old/dnsdist-1.9.7/dnsdist-proxy-protocol.hh 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist-proxy-protocol.hh 2025-05-20 11:13:25.000000000 +0200 @@ -29,7 +29,7 @@ std::string getProxyProtocolPayload(const DNSQuestion& dq); -bool addProxyProtocol(DNSQuestion& dq, size_t* proxyProtocolPayloadSize = nullptr); +bool addProxyProtocol(DNSQuestion& dnsQuestion, size_t* proxyProtocolPayloadSize = nullptr); bool addProxyProtocol(DNSQuestion& dq, const std::string& payload); bool addProxyProtocol(PacketBuffer& buffer, const std::string& payload); bool addProxyProtocol(PacketBuffer& buffer, bool tcp, const ComboAddress& source, const ComboAddress& destination, const std::vector<ProxyProtocolValue>& values); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist-tcp-upstream.hh new/dnsdist-1.9.10/dnsdist-tcp-upstream.hh --- old/dnsdist-1.9.7/dnsdist-tcp-upstream.hh 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist-tcp-upstream.hh 2025-05-20 11:13:25.000000000 +0200 @@ -116,9 +116,9 @@ return false; } - std::shared_ptr<TCPConnectionToBackend> getOwnedDownstreamConnection(const std::shared_ptr<DownstreamState>& backend, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs); std::shared_ptr<TCPConnectionToBackend> getDownstreamConnection(std::shared_ptr<DownstreamState>& backend, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs, const struct timeval& now); void registerOwnedDownstreamConnection(std::shared_ptr<TCPConnectionToBackend>& conn); + void clearOwnedDownstreamConnections(const std::shared_ptr<DownstreamState>& downstream); static size_t clearAllDownstreamConnections(); @@ -216,4 +216,5 @@ bool d_proxyProtocolPayloadHasTLV{false}; bool d_lastIOBlocked{false}; bool d_hadErrors{false}; + bool d_handlingIO{false}; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist-tcp.cc new/dnsdist-1.9.10/dnsdist-tcp.cc --- old/dnsdist-1.9.7/dnsdist-tcp.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist-tcp.cc 2025-05-20 11:13:25.000000000 +0200 @@ -114,14 +114,46 @@ return t_downstreamTCPConnectionsManager.clear(); } +static std::pair<std::shared_ptr<TCPConnectionToBackend>, bool> getOwnedDownstreamConnection(std::map<std::shared_ptr<DownstreamState>, std::deque<std::shared_ptr<TCPConnectionToBackend>>>& ownedConnectionsToBackend, const std::shared_ptr<DownstreamState>& backend, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs) +{ + bool tlvsMismatch = false; + auto connIt = ownedConnectionsToBackend.find(backend); + if (connIt == ownedConnectionsToBackend.end()) { + DEBUGLOG("no owned connection found for " << backend->getName()); + return {nullptr, tlvsMismatch}; + } + + for (auto& conn : connIt->second) { + if (conn->canBeReused(true)) { + if (conn->matchesTLVs(tlvs)) { + DEBUGLOG("Got one owned connection accepting more for " << backend->getName()); + conn->setReused(); + ++backend->tcpReusedConnections; + return {conn, tlvsMismatch}; + } + DEBUGLOG("Found one connection to " << backend->getName() << " but with different TLV values"); + tlvsMismatch = true; + } + DEBUGLOG("not accepting more for " << backend->getName()); + } + + return {nullptr, tlvsMismatch}; +} + std::shared_ptr<TCPConnectionToBackend> IncomingTCPConnectionState::getDownstreamConnection(std::shared_ptr<DownstreamState>& backend, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs, const struct timeval& now) { - auto downstream = getOwnedDownstreamConnection(backend, tlvs); + auto [downstream, tlvsMismatch] = getOwnedDownstreamConnection(d_ownedConnectionsToBackend, backend, tlvs); if (!downstream) { + if (backend->d_config.useProxyProtocol && tlvsMismatch) { + clearOwnedDownstreamConnections(backend); + } + /* we don't have a connection to this backend owned yet, let's get one (it might not be a fresh one, though) */ downstream = t_downstreamTCPConnectionsManager.getConnectionToDownstream(d_threadData.mplexer, backend, now, std::string()); - if (backend->d_config.useProxyProtocol) { + // if we had an existing connection but the TLVs are different, they are likely unique per query so do not bother keeping the connection + // around + if (backend->d_config.useProxyProtocol && !tlvsMismatch) { registerOwnedDownstreamConnection(downstream); } } @@ -272,29 +304,32 @@ d_state = State::waitingForQuery; } -std::shared_ptr<TCPConnectionToBackend> IncomingTCPConnectionState::getOwnedDownstreamConnection(const std::shared_ptr<DownstreamState>& backend, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs) +void IncomingTCPConnectionState::registerOwnedDownstreamConnection(std::shared_ptr<TCPConnectionToBackend>& conn) { - auto connIt = d_ownedConnectionsToBackend.find(backend); - if (connIt == d_ownedConnectionsToBackend.end()) { - DEBUGLOG("no owned connection found for " << backend->getName()); - return nullptr; - } + const auto& downstream = conn->getDS(); - for (auto& conn : connIt->second) { - if (conn->canBeReused(true) && conn->matchesTLVs(tlvs)) { - DEBUGLOG("Got one owned connection accepting more for " << backend->getName()); - conn->setReused(); - return conn; - } - DEBUGLOG("not accepting more for " << backend->getName()); - } + auto& queue = d_ownedConnectionsToBackend[downstream]; + // how many proxy-protocol enabled connections do we want to keep around? + // - they are only usable for this incoming connection because of the proxy protocol header containing the source and destination addresses and ports + // - if we have TLV values, and they are unique per query, keeping these is useless + // - if there is no, or identical, TLV values, a single outgoing connection is enough if maxInFlight == 1, or if incoming maxInFlight == outgoing maxInFlight + // so it makes sense to keep a few of them around if incoming maxInFlight is greater than outgoing maxInFlight - return nullptr; + auto incomingMaxInFlightQueriesPerConn = d_ci.cs->d_maxInFlightQueriesPerConn; + incomingMaxInFlightQueriesPerConn = std::max(incomingMaxInFlightQueriesPerConn, static_cast<size_t>(1U)); + auto outgoingMaxInFlightQueriesPerConn = downstream->d_config.d_maxInFlightQueriesPerConn; + outgoingMaxInFlightQueriesPerConn = std::max(outgoingMaxInFlightQueriesPerConn, static_cast<size_t>(1U)); + size_t maxCachedOutgoingConnections = std::min(static_cast<size_t>(incomingMaxInFlightQueriesPerConn / outgoingMaxInFlightQueriesPerConn), static_cast<size_t>(5U)); + + queue.push_front(conn); + if (queue.size() > maxCachedOutgoingConnections) { + queue.pop_back(); + } } -void IncomingTCPConnectionState::registerOwnedDownstreamConnection(std::shared_ptr<TCPConnectionToBackend>& conn) +void IncomingTCPConnectionState::clearOwnedDownstreamConnections(const std::shared_ptr<DownstreamState>& downstream) { - d_ownedConnectionsToBackend[conn->getDS()].push_front(conn); + d_ownedConnectionsToBackend.erase(downstream); } /* called when the buffer has been set and the rules have been processed, and only from handleIO (sometimes indirectly via handleQuery) */ @@ -1053,8 +1088,39 @@ return false; } +class HandlingIOGuard +{ +public: + HandlingIOGuard(bool& handlingIO) : + d_handlingIO(handlingIO) + { + } + HandlingIOGuard(const HandlingIOGuard&) = delete; + HandlingIOGuard(HandlingIOGuard&&) = delete; + HandlingIOGuard& operator=(const HandlingIOGuard& rhs) = delete; + HandlingIOGuard& operator=(HandlingIOGuard&&) = delete; + ~HandlingIOGuard() + { + d_handlingIO = false; + } + +private: + bool& d_handlingIO; +}; + void IncomingTCPConnectionState::handleIO() { + // let's make sure we are not already in handleIO() below in the stack: + // this might happen when we have a response available on the backend socket + // right after forwarding the query, and then a query waiting for us on the + // client socket right after forwarding the response, and then a response available + // on the backend socket right after forwarding the query.. you get the idea. + if (d_handlingIO) { + return; + } + d_handlingIO = true; + HandlingIOGuard reentryGuard(d_handlingIO); + // why do we loop? Because the TLS layer does buffering, and thus can have data ready to read // even though the underlying socket is not ready, so we need to actually ask for the data first IOState iostate = IOState::Done; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist-web.cc new/dnsdist-1.9.10/dnsdist-web.cc --- old/dnsdist-1.9.7/dnsdist-web.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist-web.cc 2025-05-20 11:13:25.000000000 +0200 @@ -913,7 +913,7 @@ output << "dnsdist_info{version=\"" << VERSION << "\"} " << "1" << "\n"; resp.body = output.str(); - resp.headers["Content-Type"] = "text/plain"; + resp.headers["Content-Type"] = "text/plain; version=0.0.4"; } #endif /* DISABLE_PROMETHEUS */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist-xsk.cc new/dnsdist-1.9.10/dnsdist-xsk.cc --- old/dnsdist-1.9.7/dnsdist-xsk.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist-xsk.cc 2025-05-20 11:13:25.000000000 +0200 @@ -48,7 +48,7 @@ if ((pollfds[0].revents & POLLIN) != 0) { needNotify = true; xskInfo->cleanSocketNotification(); - xskInfo->processIncomingFrames([&](XskPacket& packet) { + xskInfo->processIncomingFrames([&](XskPacket packet) { if (packet.getDataLen() < sizeof(dnsheader)) { xskInfo->markAsFree(packet); return; @@ -167,7 +167,7 @@ if ((fds.at(fdIndex).revents & POLLIN) != 0) { ready--; const auto& info = xsk->getWorkerByDescriptor(fds.at(fdIndex).fd); - info->processOutgoingFrames([&](XskPacket& packet) { + info->processOutgoingFrames([&](XskPacket packet) { if ((packet.getFlags() & XskPacket::UPDATE) == 0) { xsk->markAsFree(packet); return; @@ -202,7 +202,7 @@ while (!xskInfo->hasIncomingFrames()) { xskInfo->waitForXskSocket(); } - xskInfo->processIncomingFrames([&](XskPacket& packet) { + xskInfo->processIncomingFrames([&](XskPacket packet) { if (XskProcessQuery(*clientState, holders, packet)) { packet.updatePacket(); xskInfo->pushToSendQueue(packet); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist.1 new/dnsdist-1.9.10/dnsdist.1 --- old/dnsdist-1.9.7/dnsdist.1 2024-10-03 17:33:43.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist.1 2025-05-20 11:14:13.000000000 +0200 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DNSDIST" "1" "Oct 03, 2024" "" "dnsdist" +.TH "DNSDIST" "1" "May 20, 2025" "" "dnsdist" .SH NAME dnsdist \- A DNS and DoS aware, scriptable loadbalancer .SH SYNOPSIS diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist.cc new/dnsdist-1.9.10/dnsdist.cc --- old/dnsdist-1.9.7/dnsdist.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist.cc 2025-05-20 11:13:25.000000000 +0200 @@ -579,10 +579,12 @@ zeroScope = false; } uint32_t cacheKey = dr.ids.cacheKey; - if (dr.ids.protocol == dnsdist::Protocol::DoH && dr.ids.forwardedOverUDP) { - cacheKey = dr.ids.cacheKeyUDP; + if (dr.ids.protocol == dnsdist::Protocol::DoH && !dr.ids.forwardedOverUDP) { + cacheKey = dr.ids.cacheKeyTCP; + // disable zeroScope in that case, as we only have the "no-ECS" cache key for UDP + zeroScope = false; } - else if (zeroScope) { + if (zeroScope) { // if zeroScope, pass the pre-ECS hash-key and do not pass the subnet to the cache cacheKey = dr.ids.cacheKeyNoECS; } @@ -1441,6 +1443,13 @@ const auto& policy = poolPolicy != nullptr ? *poolPolicy : *(holders.policy); const auto servers = serverPool->getServers(); selectedBackend = policy.getSelectedBackend(*servers, dq); + bool willBeForwardedOverUDP = !dq.overTCP() || dq.ids.protocol == dnsdist::Protocol::DoH; + if (selectedBackend && selectedBackend->isTCPOnly()) { + willBeForwardedOverUDP = false; + } + else if (!selectedBackend) { + willBeForwardedOverUDP = !serverPool->isTCPOnly(); + } uint32_t allowExpired = selectedBackend ? 0 : g_staleCacheEntriesTTL; @@ -1453,7 +1462,7 @@ // we need ECS parsing (parseECS) to be true so we can be sure that the initial incoming query did not have an existing // ECS option, which would make it unsuitable for the zero-scope feature. if (dq.ids.packetCache && !dq.ids.skipCache && (!selectedBackend || !selectedBackend->d_config.disableZeroScope) && dq.ids.packetCache->isECSParsingEnabled()) { - if (dq.ids.packetCache->get(dq, dq.getHeader()->id, &dq.ids.cacheKeyNoECS, dq.ids.subnet, dq.ids.dnssecOK, !dq.overTCP(), allowExpired, false, true, false)) { + if (dq.ids.packetCache->get(dq, dq.getHeader()->id, &dq.ids.cacheKeyNoECS, dq.ids.subnet, dq.ids.dnssecOK, willBeForwardedOverUDP, allowExpired, false, true, false)) { vinfolog("Packet cache hit for query for %s|%s from %s (%s, %d bytes)", dq.ids.qname.toLogString(), QType(dq.ids.qtype).toString(), dq.ids.origRemote.toStringWithPort(), dq.ids.protocol.toString(), dq.getData().size()); @@ -1479,14 +1488,11 @@ } if (dq.ids.packetCache && !dq.ids.skipCache) { - bool forwardedOverUDP = !dq.overTCP(); - if (selectedBackend && selectedBackend->isTCPOnly()) { - forwardedOverUDP = false; - } - - /* we do not record a miss for queries received over DoH and forwarded over TCP + /* First lookup, which takes into account how the protocol over which the query will be forwarded. + For DoH, this lookup is done with the protocol set to TCP but we will retry over UDP below, + therefore we do not record a miss for queries received over DoH and forwarded over TCP yet, as we will do a second-lookup */ - if (dq.ids.packetCache->get(dq, dq.getHeader()->id, &dq.ids.cacheKey, dq.ids.subnet, dq.ids.dnssecOK, forwardedOverUDP, allowExpired, false, true, dq.ids.protocol != dnsdist::Protocol::DoH || forwardedOverUDP)) { + if (dq.ids.packetCache->get(dq, dq.getHeader()->id, dq.ids.protocol == dnsdist::Protocol::DoH ? &dq.ids.cacheKeyTCP : &dq.ids.cacheKey, dq.ids.subnet, dq.ids.dnssecOK, dq.ids.protocol != dnsdist::Protocol::DoH && willBeForwardedOverUDP, allowExpired, false, true, dq.ids.protocol != dnsdist::Protocol::DoH || !willBeForwardedOverUDP)) { dnsdist::PacketMangling::editDNSHeaderFromPacket(dq.getMutableData(), [flags=dq.ids.origFlags](dnsheader& header) { restoreFlags(&header, flags); @@ -1503,9 +1509,10 @@ ++dq.ids.cs->responses; return ProcessQueryResult::SendAnswer; } - else if (dq.ids.protocol == dnsdist::Protocol::DoH && !forwardedOverUDP) { - /* do a second-lookup for UDP responses, but we do not want TC=1 answers */ - if (dq.ids.packetCache->get(dq, dq.getHeader()->id, &dq.ids.cacheKeyUDP, dq.ids.subnet, dq.ids.dnssecOK, true, allowExpired, false, false, true)) { + if (dq.ids.protocol == dnsdist::Protocol::DoH && willBeForwardedOverUDP) { + /* do a second-lookup for responses received over UDP, but we do not want TC=1 answers */ + /* we need to be careful to keep the existing cache-key (TCP) */ + if (dq.ids.packetCache->get(dq, dq.getHeader()->id, &dq.ids.cacheKey, dq.ids.subnet, dq.ids.dnssecOK, true, allowExpired, false, false, true)) { if (!prepareOutgoingResponse(holders, *dq.ids.cs, dq, true)) { return ProcessQueryResult::Drop; } @@ -1689,9 +1696,13 @@ bool doh = dnsQuestion.ids.du != nullptr; bool failed = false; + dnsQuestion.ids.d_proxyProtocolPayloadSize = 0; if (downstream->d_config.useProxyProtocol) { try { - addProxyProtocol(dnsQuestion, &dnsQuestion.ids.d_proxyProtocolPayloadSize); + size_t proxyProtocolPayloadSize = 0; + if (addProxyProtocol(dnsQuestion, &proxyProtocolPayloadSize)) { + dnsQuestion.ids.d_proxyProtocolPayloadSize += proxyProtocolPayloadSize; + } } catch (const std::exception& e) { vinfolog("Adding proxy protocol payload to %s query from %s failed: %s", (dnsQuestion.ids.du ? "DoH" : ""), dnsQuestion.ids.origDest.toStringWithPort(), e.what()); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist.hh new/dnsdist-1.9.10/dnsdist.hh --- old/dnsdist-1.9.7/dnsdist.hh 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist.hh 2025-05-20 11:13:25.000000000 +0200 @@ -971,7 +971,7 @@ return d_config.d_tcpOnly || d_config.d_tcpCheck || d_tlsCtx != nullptr; } - bool isTCPOnly() const + [[nodiscard]] bool isTCPOnly() const { return d_config.d_tcpOnly || d_tlsCtx != nullptr; } @@ -1071,10 +1071,15 @@ const std::shared_ptr<const ServerPolicy::NumberedServerVector> getServers(); void addServer(shared_ptr<DownstreamState>& server); void removeServer(shared_ptr<DownstreamState>& server); + bool isTCPOnly() const + { + return d_tcpOnly; + } private: SharedLockGuarded<std::shared_ptr<const ServerPolicy::NumberedServerVector>> d_servers; bool d_useECS{false}; + bool d_tcpOnly{false}; }; enum ednsHeaderFlags { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/dnsdist.service.in new/dnsdist-1.9.10/dnsdist.service.in --- old/dnsdist-1.9.7/dnsdist.service.in 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/dnsdist.service.in 2025-05-20 11:13:25.000000000 +0200 @@ -44,7 +44,7 @@ ProtectKernelModules=true ProtectKernelTunables=true ProtectSystem=full -RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK AF_UNIX AF_XDP RestrictNamespaces=true RestrictRealtime=true RestrictSUIDSGID=true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/doh3.cc new/dnsdist-1.9.10/doh3.cc --- old/dnsdist-1.9.7/doh3.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/doh3.cc 2025-05-20 11:13:25.000000000 +0200 @@ -751,7 +751,11 @@ } if (headers.at(":method") == "POST") { +#if defined(HAVE_QUICHE_H3_EVENT_HEADERS_HAS_MORE_FRAMES) + if (!quiche_h3_event_headers_has_more_frames(event)) { +#else if (!quiche_h3_event_headers_has_body(event)) { +#endif handleImmediateError("Empty POST query"); } return; @@ -908,14 +912,14 @@ if (!quiche_version_is_supported(version)) { DEBUGLOG("Unsupported version"); ++frontend.d_doh3UnsupportedVersionErrors; - handleVersionNegociation(sock, clientConnID, serverConnID, client, localAddr, buffer); + handleVersionNegotiation(sock, clientConnID, serverConnID, client, localAddr, buffer, clientState.local.isUnspecified()); continue; } if (token_len == 0) { /* stateless retry */ DEBUGLOG("No token received"); - handleStatelessRetry(sock, clientConnID, serverConnID, client, localAddr, version, buffer); + handleStatelessRetry(sock, clientConnID, serverConnID, client, localAddr, version, buffer, clientState.local.isUnspecified()); continue; } @@ -962,7 +966,7 @@ processH3Events(clientState, frontend, conn->get(), client, serverConnID, buffer); - flushEgress(sock, conn->get().d_conn, client, localAddr, buffer); + flushEgress(sock, conn->get().d_conn, client, localAddr, buffer, clientState.local.isUnspecified()); } else { DEBUGLOG("Connection not established"); @@ -1007,7 +1011,7 @@ for (auto conn = frontend->d_server_config->d_connections.begin(); conn != frontend->d_server_config->d_connections.end();) { quiche_conn_on_timeout(conn->second.d_conn.get()); - flushEgress(sock, conn->second.d_conn, conn->second.d_peer, conn->second.d_localAddr, buffer); + flushEgress(sock, conn->second.d_conn, conn->second.d_peer, conn->second.d_localAddr, buffer, clientState->local.isUnspecified()); if (quiche_conn_is_closed(conn->second.d_conn.get())) { #ifdef DEBUGLOG_ENABLED diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/doq-common.cc new/dnsdist-1.9.10/doq-common.cc --- old/dnsdist-1.9.7/doq-common.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/doq-common.cc 2025-05-20 11:13:25.000000000 +0200 @@ -126,10 +126,22 @@ } } -static void sendFromTo(Socket& sock, const ComboAddress& peer, const ComboAddress& local, PacketBuffer& buffer) +static void sendFromTo(Socket& sock, const ComboAddress& peer, const ComboAddress& local, PacketBuffer& buffer, [[maybe_unused]] bool socketBoundToAny) { - const int flags = 0; - if (local.sin4.sin_family == 0) { + /* we only want to specify the source address to use if we were able to + either harvest it from the incoming packet, or if our socket is already + bound to a specific address */ + bool setSourceAddress = local.sin4.sin_family != 0; +#if defined(__FreeBSD__) || defined(__DragonFly__) + /* FreeBSD and DragonFlyBSD refuse the use of IP_SENDSRCADDR on a socket that is bound to a + specific address, returning EINVAL in that case. */ + if (!socketBoundToAny) { + setSourceAddress = false; + } +#endif /* __FreeBSD__ || __DragonFly__ */ + + if (!setSourceAddress) { + const int flags = 0; // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) auto ret = sendto(sock.getHandle(), buffer.data(), buffer.size(), flags, reinterpret_cast<const struct sockaddr*>(&peer), peer.getSocklen()); if (ret < 0) { @@ -147,7 +159,7 @@ } } -void handleStatelessRetry(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, const ComboAddress& localAddr, uint32_t version, PacketBuffer& buffer) +void handleStatelessRetry(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, const ComboAddress& localAddr, uint32_t version, PacketBuffer& buffer, bool socketBoundToAny) { auto newServerConnID = getCID(); if (!newServerConnID) { @@ -170,10 +182,10 @@ } buffer.resize(static_cast<size_t>(written)); - sendFromTo(sock, peer, localAddr, buffer); + sendFromTo(sock, peer, localAddr, buffer, socketBoundToAny); } -void handleVersionNegociation(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, const ComboAddress& localAddr, PacketBuffer& buffer) +void handleVersionNegotiation(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, const ComboAddress& localAddr, PacketBuffer& buffer, bool socketBoundToAny) { buffer.resize(MAX_DATAGRAM_SIZE); @@ -187,10 +199,10 @@ } buffer.resize(static_cast<size_t>(written)); - sendFromTo(sock, peer, localAddr, buffer); + sendFromTo(sock, peer, localAddr, buffer, socketBoundToAny); } -void flushEgress(Socket& sock, QuicheConnection& conn, const ComboAddress& peer, const ComboAddress& localAddr, PacketBuffer& buffer) +void flushEgress(Socket& sock, QuicheConnection& conn, const ComboAddress& peer, const ComboAddress& localAddr, PacketBuffer& buffer, bool socketBoundToAny) { buffer.resize(MAX_DATAGRAM_SIZE); quiche_send_info send_info; @@ -206,7 +218,7 @@ } // FIXME pacing (as send_info.at should tell us when to send the packet) ? buffer.resize(static_cast<size_t>(written)); - sendFromTo(sock, peer, localAddr, buffer); + sendFromTo(sock, peer, localAddr, buffer, socketBoundToAny); } } @@ -312,9 +324,7 @@ This is indicated by setting the family to 0 which is acted upon in sendUDPResponse() and DelayedPacket::(). */ - const ComboAddress bogusV4("0.0.0.0:0"); - const ComboAddress bogusV6("[::]:0"); - if ((localAddr.sin4.sin_family == AF_INET && localAddr == bogusV4) || (localAddr.sin4.sin_family == AF_INET6 && localAddr == bogusV6)) { + if (localAddr.isUnspecified()) { localAddr.sin4.sin_family = 0; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/doq-common.hh new/dnsdist-1.9.10/doq-common.hh --- old/dnsdist-1.9.7/doq-common.hh 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/doq-common.hh 2025-05-20 11:13:25.000000000 +0200 @@ -92,9 +92,9 @@ std::optional<PacketBuffer> getCID(); PacketBuffer mintToken(const PacketBuffer& dcid, const ComboAddress& peer); std::optional<PacketBuffer> validateToken(const PacketBuffer& token, const ComboAddress& peer); -void handleStatelessRetry(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, const ComboAddress& localAddr, uint32_t version, PacketBuffer& buffer); -void handleVersionNegociation(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, const ComboAddress& localAddr, PacketBuffer& buffer); -void flushEgress(Socket& sock, QuicheConnection& conn, const ComboAddress& peer, const ComboAddress& localAddr, PacketBuffer& buffer); +void handleStatelessRetry(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, const ComboAddress& localAddr, uint32_t version, PacketBuffer& buffer, bool socketBoundToAny); +void handleVersionNegotiation(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, const ComboAddress& localAddr, PacketBuffer& buffer, bool socketBoundToAny); +void flushEgress(Socket& sock, QuicheConnection& conn, const ComboAddress& peer, const ComboAddress& localAddr, PacketBuffer& buffer, bool socketBoundToAny); void configureQuiche(QuicheConfig& config, const QuicheParams& params, bool isHTTP); bool recvAsync(Socket& socket, PacketBuffer& buffer, ComboAddress& clientAddr, ComboAddress& localAddr); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/doq.cc new/dnsdist-1.9.10/doq.cc --- old/dnsdist-1.9.7/doq.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/doq.cc 2025-05-20 11:13:25.000000000 +0200 @@ -718,14 +718,14 @@ if (!quiche_version_is_supported(version)) { DEBUGLOG("Unsupported version"); ++frontend.d_doqUnsupportedVersionErrors; - handleVersionNegociation(sock, clientConnID, serverConnID, client, localAddr, buffer); + handleVersionNegotiation(sock, clientConnID, serverConnID, client, localAddr, buffer, clientState.local.isUnspecified()); continue; } if (token_len == 0) { /* stateless retry */ DEBUGLOG("No token received"); - handleStatelessRetry(sock, clientConnID, serverConnID, client, localAddr, version, buffer); + handleStatelessRetry(sock, clientConnID, serverConnID, client, localAddr, version, buffer, clientState.local.isUnspecified()); continue; } @@ -766,7 +766,7 @@ handleReadableStream(frontend, clientState, *conn, streamID, client, serverConnID); } - flushEgress(sock, conn->get().d_conn, client, localAddr, buffer); + flushEgress(sock, conn->get().d_conn, client, localAddr, buffer, clientState.local.isUnspecified()); } else { DEBUGLOG("Connection not established"); @@ -811,7 +811,7 @@ for (auto conn = frontend->d_server_config->d_connections.begin(); conn != frontend->d_server_config->d_connections.end();) { quiche_conn_on_timeout(conn->second.d_conn.get()); - flushEgress(sock, conn->second.d_conn, conn->second.d_peer, conn->second.d_localAddr, buffer); + flushEgress(sock, conn->second.d_conn, conn->second.d_peer, conn->second.d_localAddr, buffer, clientState->local.isUnspecified()); if (quiche_conn_is_closed(conn->second.d_conn.get())) { #ifdef DEBUGLOG_ENABLED diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/iputils.hh new/dnsdist-1.9.10/iputils.hh --- old/dnsdist-1.9.7/iputils.hh 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/iputils.hh 2025-05-20 11:13:25.000000000 +0200 @@ -266,6 +266,13 @@ return true; } + [[nodiscard]] bool isUnspecified() const + { + const ComboAddress unspecifiedV4("0.0.0.0:0"); + const ComboAddress unspecifiedV6("[::]:0"); + return *this == unspecifiedV4 || *this == unspecifiedV6; + } + ComboAddress mapToIPv4() const { if(!isMappedIPv4()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/libssl.cc new/dnsdist-1.9.10/libssl.cc --- old/dnsdist-1.9.7/libssl.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/libssl.cc 2025-05-20 11:13:25.000000000 +0200 @@ -687,6 +687,22 @@ file.close(); } +void OpenSSLTLSTicketKeysRing::loadTicketsKey(const std::string& key) +{ + bool keyLoaded = false; + try { + auto newKey = std::make_shared<OpenSSLTLSTicketKey>(key); + addKey(std::move(newKey)); + keyLoaded = true; + } + catch (const std::exception& e) { + /* if we haven't been able to load at least one key, fail */ + if (!keyLoaded) { + throw; + } + } +} + void OpenSSLTLSTicketKeysRing::rotateTicketsKey(time_t /* now */) { auto newKey = std::make_shared<OpenSSLTLSTicketKey>(); @@ -729,6 +745,26 @@ #endif /* HAVE_LIBSODIUM */ } +// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init): d_name, d_cipherKey and d_hmacKey are initialized +OpenSSLTLSTicketKey::OpenSSLTLSTicketKey(const std::string& key) +{ + if (key.size() != (sizeof(d_name) + sizeof(d_cipherKey) + sizeof(d_hmacKey))) { + throw std::runtime_error("Unable to load a ticket key from given data"); + } + size_t from = 0; + memcpy(&d_name, &key.at(from), sizeof(d_name)); + from += sizeof(d_name); + memcpy(&d_cipherKey, &key.at(from), sizeof(d_cipherKey)); + from += sizeof(d_cipherKey); + memcpy(&d_hmacKey, &key.at(from), sizeof(d_hmacKey)); + +#ifdef HAVE_LIBSODIUM + sodium_mlock(&d_name, sizeof(d_name)); + sodium_mlock(&d_cipherKey, sizeof(d_cipherKey)); + sodium_mlock(&d_hmacKey, sizeof(d_hmacKey)); +#endif /* HAVE_LIBSODIUM */ +} + OpenSSLTLSTicketKey::~OpenSSLTLSTicketKey() { #ifdef HAVE_LIBSODIUM diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/libssl.hh new/dnsdist-1.9.10/libssl.hh --- old/dnsdist-1.9.7/libssl.hh 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/libssl.hh 2025-05-20 11:13:25.000000000 +0200 @@ -93,6 +93,7 @@ public: OpenSSLTLSTicketKey(); OpenSSLTLSTicketKey(std::ifstream& file); + OpenSSLTLSTicketKey(const std::string& key); ~OpenSSLTLSTicketKey(); bool nameMatches(const unsigned char name[TLS_TICKETS_KEY_NAME_SIZE]) const; @@ -122,6 +123,7 @@ std::shared_ptr<OpenSSLTLSTicketKey> getDecryptionKey(unsigned char name[TLS_TICKETS_KEY_NAME_SIZE], bool& activeKey); size_t getKeysCount(); void loadTicketsKeys(const std::string& keyFile); + void loadTicketsKey(const std::string& key); void rotateTicketsKey(time_t now); private: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/m4/pdns_with_quiche.m4 new/dnsdist-1.9.10/m4/pdns_with_quiche.m4 --- old/dnsdist-1.9.7/m4/pdns_with_quiche.m4 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/m4/pdns_with_quiche.m4 2025-05-20 11:13:25.000000000 +0200 @@ -10,16 +10,23 @@ AS_IF([test "x$with_quiche" != "xno"], [ AS_IF([test "x$with_quiche" = "xyes" -o "x$with_quiche" = "xauto"], [ - PKG_CHECK_MODULES([QUICHE], [quiche >= 0.22.0], [ + PKG_CHECK_MODULES([QUICHE], [quiche >= 0.23.0], [ [HAVE_QUICHE=1] AC_DEFINE([HAVE_QUICHE], [1], [Define to 1 if you have quiche]) + AC_DEFINE([HAVE_QUICHE_H3_EVENT_HEADERS_HAS_MORE_FRAMES], [1], [Define to 1 if the Quiche API has quiche_h3_event_headers_has_more_frames instead of quiche_h3_event_headers_has_body]) AC_DEFINE([HAVE_QUICHE_STREAM_ERROR_CODES], [1], [Define to 1 if the Quiche API includes error code in quiche_conn_stream_recv and quiche_conn_stream_send]) ], [ - # Quiche is older than 0.22.0, or no Quiche at all - PKG_CHECK_MODULES([QUICHE], [quiche >= 0.15.0], [ + PKG_CHECK_MODULES([QUICHE], [quiche >= 0.22.0], [ [HAVE_QUICHE=1] AC_DEFINE([HAVE_QUICHE], [1], [Define to 1 if you have quiche]) - ], [ : ]) + AC_DEFINE([HAVE_QUICHE_STREAM_ERROR_CODES], [1], [Define to 1 if the Quiche API includes error code in quiche_conn_stream_recv and quiche_conn_stream_send]) + ], [ + # Quiche is older than 0.22.0, or no Quiche at all + PKG_CHECK_MODULES([QUICHE], [quiche >= 0.15.0], [ + [HAVE_QUICHE=1] + AC_DEFINE([HAVE_QUICHE], [1], [Define to 1 if you have quiche]) + ], [ : ]) + ]) ]) ]) ]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/noinitvector.hh new/dnsdist-1.9.10/noinitvector.hh --- old/dnsdist-1.9.7/noinitvector.hh 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/noinitvector.hh 2025-05-20 11:13:25.000000000 +0200 @@ -1,5 +1,6 @@ #pragma once +#include <cstdint> #include <memory> #include <new> #include <utility> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/tcpiohandler.cc new/dnsdist-1.9.10/tcpiohandler.cc --- old/dnsdist-1.9.7/tcpiohandler.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/tcpiohandler.cc 2025-05-20 11:13:25.000000000 +0200 @@ -857,6 +857,15 @@ } } + void loadTicketsKey(const std::string& key) final + { + d_feContext->d_ticketKeys.loadTicketsKey(key); + + if (d_ticketsKeyRotationDelay > 0) { + d_ticketsKeyNextRotation = time(nullptr) + d_ticketsKeyRotationDelay; + } + } + size_t getTicketsKeysCount() override { return d_feContext->d_ticketKeys.getKeysCount(); @@ -1002,7 +1011,24 @@ safe_memory_lock(d_key.data, d_key.size); } - GnuTLSTicketsKey(const std::string& keyFile) + GnuTLSTicketsKey(const std::string& key) + { + /* to be sure we are loading the correct amount of data, which + may change between versions, let's generate a correct key first */ + if (gnutls_session_ticket_key_generate(&d_key) != GNUTLS_E_SUCCESS) { + throw std::runtime_error("Error generating tickets key (before parsing key file) for TLS context"); + } + + safe_memory_lock(d_key.data, d_key.size); + if (key.size() != d_key.size) { + safe_memory_release(d_key.data, d_key.size); + gnutls_free(d_key.data); + d_key.data = nullptr; + throw std::runtime_error("Invalid GnuTLS ticket key size"); + } + memcpy(d_key.data, key.data(), key.size()); + } + GnuTLSTicketsKey(std::ifstream& file) { /* to be sure we are loading the correct amount of data, which may change between versions, let's generate a correct key first */ @@ -1013,15 +1039,12 @@ safe_memory_lock(d_key.data, d_key.size); try { - ifstream file(keyFile); file.read(reinterpret_cast<char*>(d_key.data), d_key.size); if (file.fail()) { - file.close(); - throw std::runtime_error("Invalid GnuTLS tickets key file " + keyFile); + throw std::runtime_error("Invalid GnuTLS tickets key file"); } - file.close(); } catch (const std::exception& e) { safe_memory_release(d_key.data, d_key.size); @@ -1813,14 +1836,26 @@ auto newKey = std::make_shared<GnuTLSTicketsKey>(); addTicketsKey(now, std::move(newKey)); } - void loadTicketsKeys(const std::string& file) final + void loadTicketsKey(const std::string& key) final + { + if (!d_enableTickets) { + return; + } + + auto newKey = std::make_shared<GnuTLSTicketsKey>(key); + addTicketsKey(time(nullptr), std::move(newKey)); + } + + void loadTicketsKeys(const std::string& keyFile) final { if (!d_enableTickets) { return; } + std::ifstream file(keyFile); auto newKey = std::make_shared<GnuTLSTicketsKey>(file); addTicketsKey(time(nullptr), std::move(newKey)); + file.close(); } size_t getTicketsKeysCount() override diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/tcpiohandler.hh new/dnsdist-1.9.10/tcpiohandler.hh --- old/dnsdist-1.9.7/tcpiohandler.hh 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/tcpiohandler.hh 2025-05-20 11:13:25.000000000 +0200 @@ -81,6 +81,10 @@ { throw std::runtime_error("This TLS backend does not have the capability to load a tickets key from a file"); } + virtual void loadTicketsKey(const std::string& /* key */) + { + throw std::runtime_error("This TLS backend does not have the capability to load a ticket key"); + } void handleTicketsKeyRotation(time_t now) { if (d_ticketsKeyRotationDelay != 0 && now > d_ticketsKeyNextRotation) { @@ -175,6 +179,13 @@ } } + void loadTicketsKey(const std::string& key) + { + if (d_ctx != nullptr) { + d_ctx->loadTicketsKey(key); + } + } + std::shared_ptr<TLSCtx> getContext() { return std::atomic_load_explicit(&d_ctx, std::memory_order_acquire); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/test-dnsdist-lua-ffi.cc new/dnsdist-1.9.10/test-dnsdist-lua-ffi.cc --- old/dnsdist-1.9.7/test-dnsdist-lua-ffi.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/test-dnsdist-lua-ffi.cc 2025-05-20 11:13:25.000000000 +0200 @@ -373,6 +373,30 @@ BOOST_CHECK_EQUAL(ids.d_protoBufData->d_deviceID, deviceID); BOOST_CHECK_EQUAL(ids.d_protoBufData->d_deviceName, deviceName); BOOST_CHECK_EQUAL(ids.d_protoBufData->d_requestorID, requestorID); + + /* no frontend yet */ + BOOST_CHECK(dnsdist_ffi_dnsquestion_get_incoming_interface(nullptr) == nullptr); + BOOST_CHECK(dnsdist_ffi_dnsquestion_get_incoming_interface(&lightDQ) == nullptr); + { + /* frontend without and interface set */ + const std::string interface{}; + ClientState frontend(ids.origDest, false, false, 0, interface, {}, false); + ids.cs = &frontend; + const auto* itfPtr = dnsdist_ffi_dnsquestion_get_incoming_interface(&lightDQ); + BOOST_REQUIRE(itfPtr != nullptr); + BOOST_CHECK_EQUAL(std::string(itfPtr), interface); + ids.cs = nullptr; + } + { + /* frontend with interface set */ + const std::string interface{"interface-name-0"}; + ClientState frontend(ids.origDest, false, false, 0, interface, {}, false); + ids.cs = &frontend; + const auto* itfPtr = dnsdist_ffi_dnsquestion_get_incoming_interface(&lightDQ); + BOOST_REQUIRE(itfPtr != nullptr); + BOOST_CHECK_EQUAL(std::string(itfPtr), interface); + ids.cs = nullptr; + } } BOOST_AUTO_TEST_CASE(test_Response) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/test-dnsdisttcp_cc.cc new/dnsdist-1.9.10/test-dnsdisttcp_cc.cc --- old/dnsdist-1.9.7/test-dnsdisttcp_cc.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/test-dnsdisttcp_cc.cc 2025-05-20 11:13:25.000000000 +0200 @@ -4182,4 +4182,82 @@ } } +BOOST_FIXTURE_TEST_CASE(test_Pipelined_Queries_Immediate_Responses, TestFixture) +{ + auto local = getBackendAddress("1", 80); + ClientState localCS(local, true, false, 0, "", {}, true); + auto tlsCtx = std::make_shared<MockupTLSCtx>(); + localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx); + + TCPClientThreadData threadData; + threadData.mplexer = std::make_unique<MockupFDMultiplexer>(); + + timeval now{}; + gettimeofday(&now, nullptr); + + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pwQ(query, DNSName("powerdns.com."), QType::A, QClass::IN, 0); + pwQ.getHeader()->rd = 1; + pwQ.getHeader()->id = 0; + + auto querySize = static_cast<uint16_t>(query.size()); + const std::array<uint8_t, 2> sizeBytes{ static_cast<uint8_t>(querySize / 256), static_cast<uint8_t>(querySize % 256) }; + query.insert(query.begin(), sizeBytes.begin(), sizeBytes.end()); + + auto backend = std::make_shared<DownstreamState>(getBackendAddress("42", 53)); + backend->d_tlsCtx = tlsCtx; + + { + /* 1000 queries from client passed to backend (one at at time), backend answers right away */ + TEST_INIT("=> Query to backend, backend answers right away"); + const size_t nbQueries = 10000; + s_readBuffer = query; + s_backendReadBuffer = query; + + s_steps = { + { ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done }, + { ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 2 }, + { ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, query.size() - 2 }, + /* opening a connection to the backend */ + { ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done }, + { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, query.size() }, + { ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, 2 }, + { ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, query.size() - 2 }, + { ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, query.size() }, + }; + for (size_t idx = 1; idx < nbQueries; idx++) { + appendPayloadEditingID(s_readBuffer, query, idx); + appendPayloadEditingID(s_backendReadBuffer, query, idx); + + s_steps.emplace_back(ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 2); + s_steps.emplace_back(ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, query.size() - 2); + s_steps.emplace_back(ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, query.size()); + s_steps.emplace_back(ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, 2); + s_steps.emplace_back(ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, query.size() - 2); + s_steps.emplace_back(ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, query.size()); + } + s_steps.emplace_back(ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 0); + /* closing client connection */ + s_steps.emplace_back(ExpectedStep::ExpectedRequest::closeClient, IOState::Done); + /* closing a connection to the backend */ + s_steps.emplace_back(ExpectedStep::ExpectedRequest::closeBackend, IOState::Done); + + s_processQuery = [backend](DNSQuestion&, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult { + selectedBackend = backend; + return ProcessQueryResult::PassToBackend; + }; + s_processResponse = [](PacketBuffer&, DNSResponse&, bool) -> bool { + return true; + }; + + auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now); + state->handleIO(); + BOOST_CHECK_EQUAL(s_writeBuffer.size(), query.size() * nbQueries); + BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size() * nbQueries); + BOOST_CHECK_EQUAL(backend->outstanding.load(), 0U); + /* we need to clear them now, otherwise we end up with dangling pointers to the steps via the TLS context, etc */ + IncomingTCPConnectionState::clearAllDownstreamConnections(); + } +} + BOOST_AUTO_TEST_SUITE_END(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/xsk.cc new/dnsdist-1.9.10/xsk.cc --- old/dnsdist-1.9.7/xsk.cc 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/xsk.cc 2025-05-20 11:13:25.000000000 +0200 @@ -1185,7 +1185,7 @@ return d_incomingPacketsQueue.read_available() != 0U; } -void XskWorker::processIncomingFrames(const std::function<void(XskPacket& packet)>& callback) +void XskWorker::processIncomingFrames(const std::function<void(XskPacket packet)>& callback) { if (d_type == Type::OutgoingOnly) { throw std::runtime_error("Looking for incoming packets in an outgoing-only XSK Worker"); @@ -1194,7 +1194,7 @@ d_incomingPacketsQueue.consume_all(callback); } -void XskWorker::processOutgoingFrames(const std::function<void(XskPacket& packet)>& callback) +void XskWorker::processOutgoingFrames(const std::function<void(XskPacket packet)>& callback) { d_outgoingPacketsQueue.consume_all(callback); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/dnsdist-1.9.7/xsk.hh new/dnsdist-1.9.10/xsk.hh --- old/dnsdist-1.9.7/xsk.hh 2024-10-03 17:32:55.000000000 +0200 +++ new/dnsdist-1.9.10/xsk.hh 2025-05-20 11:13:25.000000000 +0200 @@ -312,8 +312,8 @@ void pushToProcessingQueue(XskPacket& packet); void pushToSendQueue(XskPacket& packet); bool hasIncomingFrames(); - void processIncomingFrames(const std::function<void(XskPacket& packet)>& callback); - void processOutgoingFrames(const std::function<void(XskPacket& packet)>& callback); + void processIncomingFrames(const std::function<void(XskPacket packet)>& callback); + void processOutgoingFrames(const std::function<void(XskPacket packet)>& callback); void markAsFree(const XskPacket& packet); // notify worker that at least one packet is available for processing void notifyWorker() const;