Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package dnsdist for openSUSE:Factory checked in at 2026-04-28 11:58:27 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/dnsdist (Old) and /work/SRC/openSUSE:Factory/.dnsdist.new.11940 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "dnsdist" Tue Apr 28 11:58:27 2026 rev:19 rq:1349637 version:2.0.5 Changes: -------- --- /work/SRC/openSUSE:Factory/dnsdist/dnsdist.changes 2026-04-01 19:52:48.054544338 +0200 +++ /work/SRC/openSUSE:Factory/.dnsdist.new.11940/dnsdist.changes 2026-04-28 12:02:23.408510222 +0200 @@ -1,0 +2,22 @@ +Mon Apr 27 13:14:13 UTC 2026 - Adam Majer <[email protected]> + +- update to 2.0.5 + https://www.dnsdist.org/changelog.html#change-2.0.5 + +- changes in 2.0.4: + https://www.dnsdist.org/changelog.html#change-2.0.4 + + * bsc#1262536 (CVE-2026-33257): Insufficient input validation of internal webserver + * bsc#1262537 (CVE-2026-33260): Insufficient input validation of internal webserver + * bsc#1262538 (CVE-2026-33254): Resource exhaustion via DoQ/DoH3 connections + * bsc#1262539 (CVE-2026-33602): Off-by-one access when processing crafted UDP responses + * bsc#1262540 (CVE-2026-33599): Out-of-bounds read in service discovery + * bsc#1262541 (CVE-2026-33598): Out-of-bounds read in cache inspection via Lua + * bsc#1262542 (CVE-2026-33597): PRSD detection denial of service + * bsc#1262543 (CVE-2026-33596): TCP backend stream ID overflow + * bsc#1262544 (CVE-2026-33595): DoQ/DoH3 excessive memory allocation + * bsc#1262545 (CVE-2026-33594): Outgoing DoH excessive memory allocation + * bsc#1262546 (CVE-2026-33593): Denial of service via crafted DNSCrypt query + + +------------------------------------------------------------------- Old: ---- dnsdist-2.0.3.tar.xz dnsdist-2.0.3.tar.xz.sig New: ---- dnsdist-2.0.5.tar.xz dnsdist-2.0.5.tar.xz.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ dnsdist.spec ++++++ --- /var/tmp/diff_new_pack.0mnrS8/_old 2026-04-28 12:02:24.168541707 +0200 +++ /var/tmp/diff_new_pack.0mnrS8/_new 2026-04-28 12:02:24.172541873 +0200 @@ -46,7 +46,7 @@ %bcond_with dnsdist_quiche %bcond_with dnsdist_xdp Name: dnsdist -Version: 2.0.3 +Version: 2.0.5 Release: 0 Summary: A highly DNS-, DoS- and abuse-aware loadbalancer License: GPL-2.0-only ++++++ _scmsync.obsinfo ++++++ --- /var/tmp/diff_new_pack.0mnrS8/_old 2026-04-28 12:02:24.272546016 +0200 +++ /var/tmp/diff_new_pack.0mnrS8/_new 2026-04-28 12:02:24.276546181 +0200 @@ -1,6 +1,6 @@ -mtime: 1774977541 -commit: 1dc807eb26742a36d2479e761c9a3339f0315b5e1204b818ad79ed5c01332dcd -url: https://src.opensuse.org/dns/dnsdist.git -revision: 1dc807eb26742a36d2479e761c9a3339f0315b5e1204b818ad79ed5c01332dcd +mtime: 1777307419 +commit: 3a4e1bc82c983adbd17f8264a3361dcde76fc3dd0bd9e059a66b678e7a3a0643 +url: https://src.opensuse.org/dns/dnsdist +revision: 3a4e1bc82c983adbd17f8264a3361dcde76fc3dd0bd9e059a66b678e7a3a0643 projectscmsync: https://src.opensuse.org/dns/_ObsPrj.git ++++++ build.specials.obscpio ++++++ ++++++ build.specials.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/.gitignore new/.gitignore --- old/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/.gitignore 2026-04-27 18:30:19.000000000 +0200 @@ -0,0 +1 @@ +.osc ++++++ dnsdist-2.0.3.tar.xz -> dnsdist-2.0.5.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/configure new/dnsdist-2.0.5/configure --- old/dnsdist-2.0.3/configure 2026-03-13 16:13:15.793822800 +0100 +++ new/dnsdist-2.0.5/configure 2026-04-22 19:41:46.068291700 +0200 @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.72 for dnsdist 2.0.3. +# Generated by GNU Autoconf 2.72 for dnsdist 2.0.5. # # # Copyright (C) 1992-1996, 1998-2017, 2020-2023 Free Software Foundation, @@ -611,8 +611,8 @@ # Identity of this package. PACKAGE_NAME='dnsdist' PACKAGE_TARNAME='dnsdist' -PACKAGE_VERSION='2.0.3' -PACKAGE_STRING='dnsdist 2.0.3' +PACKAGE_VERSION='2.0.5' +PACKAGE_STRING='dnsdist 2.0.5' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1646,7 +1646,7 @@ # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -'configure' configures dnsdist 2.0.3 to adapt to many kinds of systems. +'configure' configures dnsdist 2.0.5 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1717,7 +1717,7 @@ if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of dnsdist 2.0.3:";; + short | recursive ) echo "Configuration of dnsdist 2.0.5:";; esac cat <<\_ACEOF @@ -1954,7 +1954,7 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -dnsdist configure 2.0.3 +dnsdist configure 2.0.5 generated by GNU Autoconf 2.72 Copyright (C) 2023 Free Software Foundation, Inc. @@ -2458,7 +2458,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by dnsdist $as_me 2.0.3, which was +It was created by dnsdist $as_me 2.0.5, which was generated by GNU Autoconf 2.72. Invocation command line was $ $0$ac_configure_args_raw @@ -4154,7 +4154,7 @@ # Define the identity of the package. PACKAGE='dnsdist' - VERSION='2.0.3' + VERSION='2.0.5' printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h @@ -29452,7 +29452,7 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by dnsdist $as_me 2.0.3, which was +This file was extended by dnsdist $as_me 2.0.5, which was generated by GNU Autoconf 2.72. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -29520,7 +29520,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -dnsdist config.status 2.0.3 +dnsdist config.status 2.0.5 configured by $0, generated by GNU Autoconf 2.72, with options \\"\$ac_cs_config\\" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/configure.ac new/dnsdist-2.0.5/configure.ac --- old/dnsdist-2.0.3/configure.ac 2026-03-13 16:13:06.393366300 +0100 +++ new/dnsdist-2.0.5/configure.ac 2026-04-22 19:41:38.354256200 +0200 @@ -1,6 +1,6 @@ AC_PREREQ([2.69]) -AC_INIT([dnsdist], [2.0.3]) +AC_INIT([dnsdist], [2.0.5]) 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' old/dnsdist-2.0.3/dnscrypt.cc new/dnsdist-2.0.5/dnscrypt.cc --- old/dnsdist-2.0.3/dnscrypt.cc 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/dnscrypt.cc 2026-04-22 19:37:43.000000000 +0200 @@ -633,6 +633,9 @@ if (d_pair == nullptr) { throw std::runtime_error("Trying to compute the padding size from an invalid DNSCrypt query"); } + if (unpaddedLen > maxLen) { + throw std::runtime_error("Trying to compute the padding size for an oversized content"); + } DNSCryptNonceType nonce; memcpy(nonce.data(), d_header.clientNonce.data(), d_header.clientNonce.size()); @@ -690,6 +693,9 @@ size_t requiredSize = sizeof(responseHeader) + DNSCRYPT_MAC_SIZE + response.size(); size_t maxSize = std::min(maxResponseSize, requiredSize + DNSCRYPT_MAX_RESPONSE_PADDING_SIZE); + if (requiredSize > maxResponseSize) { + return ENOBUFS; + } uint16_t paddingSize = computePaddingSize(requiredSize, maxSize); requiredSize += paddingSize; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist-backend.cc new/dnsdist-2.0.5/dnsdist-backend.cc --- old/dnsdist-2.0.3/dnsdist-backend.cc 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist-backend.cc 2026-04-22 19:37:43.000000000 +0200 @@ -571,7 +571,7 @@ do { uint16_t selectedID = (idOffset++) % idStates.size(); - IDState& ids = idStates[selectedID]; + IDState& ids = idStates.at(selectedID); auto guard = ids.acquire(); if (!guard) { continue; @@ -615,7 +615,7 @@ return; } - auto& ids = idStates[id]; + auto& ids = idStates.at(id); auto guard = ids.acquire(); if (!guard) { /* already used */ @@ -654,11 +654,11 @@ return result; } - if (id > idStates.size()) { + if (id >= idStates.size()) { return result; } - auto& ids = idStates[id]; + auto& ids = idStates.at(id); auto guard = ids.acquire(); if (!guard) { return result; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist-concurrent-connections.cc new/dnsdist-2.0.5/dnsdist-concurrent-connections.cc --- old/dnsdist-2.0.3/dnsdist-concurrent-connections.cc 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist-concurrent-connections.cc 2026-04-22 19:37:43.000000000 +0200 @@ -164,7 +164,7 @@ return activity.front(); } -IncomingConcurrentTCPConnectionsManager::NewConnectionResult IncomingConcurrentTCPConnectionsManager::accountNewTCPConnection(const ComboAddress& from, bool isTLS) +IncomingConcurrentTCPConnectionsManager::NewConnectionResult IncomingConcurrentTCPConnectionsManager::accountNewTCPConnection(const ComboAddress& from, bool isTLS, bool isQUIC) { const auto& immutable = dnsdist::configuration::getImmutableConfiguration(); const auto maxConnsPerClient = immutable.d_maxTCPConnectionsPerClient; @@ -185,18 +185,22 @@ ++activity.tcpConnections; }; - auto checkConnectionAllowed = [now, from, maxConnsPerClient, threshold, tcpRate, tlsNewRate, tlsResumedRate, interval, isTLS, &immutable](const ClientEntry& entry) { + auto getProtocol = [isQUIC]() -> std::string { + return isQUIC ? "QUIC" : "TCP"; + }; + + auto checkConnectionAllowed = [now, from, maxConnsPerClient, threshold, tcpRate, tlsNewRate, tlsResumedRate, interval, isTLS, &immutable, &getProtocol](const ClientEntry& entry) { if (entry.d_bannedUntil != 0 && entry.d_bannedUntil >= now) { - vinfolog("Refusing TCP connection from %s: banned", from.toStringWithPort()); + vinfolog("Refusing %s connection from %s: banned", getProtocol(), from.toStringWithPort()); return NewConnectionResult::Denied; } if (maxConnsPerClient > 0 && entry.d_concurrentConnections >= maxConnsPerClient) { - vinfolog("Refusing TCP connection from %s: too many connections", from.toStringWithPort()); + vinfolog("Refusing %s connection from %s: too many connections", getProtocol(), from.toStringWithPort()); return NewConnectionResult::Denied; } if (!checkTCPConnectionsRate(entry.d_activity, now, tcpRate, tlsNewRate, tlsResumedRate, interval, isTLS)) { entry.d_bannedUntil = now + immutable.d_tcpBanDurationForExceedingTCPTLSRate; - vinfolog("Banning TCP connections from %s for %d seconds: too many new TCP/TLS connections per second", from.toStringWithPort(), immutable.d_tcpBanDurationForExceedingTCPTLSRate); + vinfolog("Banning connections from %s for %d seconds: too many new QUIC/TCP/TLS connections per second", from.toStringWithPort(), immutable.d_tcpBanDurationForExceedingTCPTLSRate); return NewConnectionResult::Denied; } @@ -208,7 +212,7 @@ if (current < threshold) { return NewConnectionResult::Allowed; } - vinfolog("Restricting TCP connection from %s: nearly reaching the maximum number of concurrent TCP connections", from.toStringWithPort()); + vinfolog("Restricting %s connection from %s: nearly reaching the maximum number of concurrent TCP connections", getProtocol(), from.toStringWithPort()); return NewConnectionResult::Restricted; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist-concurrent-connections.hh new/dnsdist-2.0.5/dnsdist-concurrent-connections.hh --- old/dnsdist-2.0.3/dnsdist-concurrent-connections.hh 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist-concurrent-connections.hh 2026-04-22 19:37:43.000000000 +0200 @@ -34,7 +34,7 @@ Denied = 1, Restricted = 2, }; - static NewConnectionResult accountNewTCPConnection(const ComboAddress& from, bool isTLS); + static NewConnectionResult accountNewTCPConnection(const ComboAddress& from, bool isTLS, bool isQUIC = false); static bool isClientOverThreshold(const ComboAddress& from); static void accountTLSNewSession(const ComboAddress& from); static void accountTLSResumedSession(const ComboAddress& from); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist-discovery.cc new/dnsdist-2.0.5/dnsdist-discovery.cc --- old/dnsdist-2.0.3/dnsdist-discovery.cc 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist-discovery.cc 2026-04-22 19:37:43.000000000 +0200 @@ -217,7 +217,7 @@ } /* we prefer the address we already know, whenever possible */ - if (tentativeAddresses.count(existingAddr) != 0) { + if (tentativeAddresses.empty() || tentativeAddresses.count(existingAddr) != 0) { tempConfig.d_addr = existingAddr; } else { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist-dynblocks.hh new/dnsdist-2.0.5/dnsdist-dynblocks.hh --- old/dnsdist-2.0.3/dnsdist-dynblocks.hh 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist-dynblocks.hh 2026-04-22 19:37:43.000000000 +0200 @@ -65,6 +65,7 @@ { } + mutable std::string fullname; const StatNode& node; const StatNode::Stat& self; const StatNode::Stat& children; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist-ecs.cc new/dnsdist-2.0.5/dnsdist-ecs.cc --- old/dnsdist-2.0.3/dnsdist-ecs.cc 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist-ecs.cc 2026-04-22 19:37:43.000000000 +0200 @@ -513,23 +513,20 @@ /* This function looks for an OPT RR, return true if a valid one was found (even if there was no options) and false otherwise. */ -bool parseEDNSOptions(const DNSQuestion& dnsQuestion) +std::optional<EDNSOptionViewMap> parseEDNSOptions(const DNSQuestion& dnsQuestion) { + EDNSOptionViewMap ednsOptions{}; const auto dnsHeader = dnsQuestion.getHeader(); - if (dnsQuestion.ednsOptions != nullptr) { - return true; - } - - // dnsQuestion.ednsOptions is mutable - dnsQuestion.ednsOptions = std::make_unique<EDNSOptionViewMap>(); - if (ntohs(dnsHeader->arcount) == 0) { /* nothing in additional so no EDNS */ - return false; + return std::nullopt; } if (ntohs(dnsHeader->ancount) != 0 || ntohs(dnsHeader->nscount) != 0 || ntohs(dnsHeader->arcount) > 1) { - return slowParseEDNSOptions(dnsQuestion.getData(), *dnsQuestion.ednsOptions); + if (slowParseEDNSOptions(dnsQuestion.getData(), ednsOptions)) { + return ednsOptions; + } + return std::nullopt; } size_t remaining = 0; @@ -538,11 +535,14 @@ if (res == 0) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - res = getEDNSOptions(reinterpret_cast<const char*>(&dnsQuestion.getData().at(optRDPosition)), remaining, *dnsQuestion.ednsOptions); - return (res == 0); + res = getEDNSOptions(reinterpret_cast<const char*>(&dnsQuestion.getData().at(optRDPosition)), remaining, ednsOptions); + if (res != 0) { + return std::nullopt; + } + return ednsOptions; } - return false; + return std::nullopt; } static bool addECSToExistingOPT(PacketBuffer& packet, size_t maximumSize, const string& newECSOption, size_t optRDLenPosition, bool& ecsAdded) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist-ecs.hh new/dnsdist-2.0.5/dnsdist-ecs.hh --- old/dnsdist-2.0.3/dnsdist-ecs.hh 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist-ecs.hh 2026-04-22 19:37:43.000000000 +0200 @@ -23,6 +23,7 @@ #include <string> +#include "ednsoptions.hh" #include "iputils.hh" #include "noinitvector.hh" @@ -46,7 +47,7 @@ bool handleEDNSClientSubnet(DNSQuestion& dnsQuestion, bool& ednsAdded, bool& ecsAdded); bool handleEDNSClientSubnet(PacketBuffer& packet, size_t maximumSize, size_t qnameWireLength, bool& ednsAdded, bool& ecsAdded, bool overrideExisting, const string& newECSOption); -bool parseEDNSOptions(const DNSQuestion& dnsQuestion); +std::optional<EDNSOptionViewMap> parseEDNSOptions(const DNSQuestion& dnsQuestion); bool queryHasEDNS(const DNSQuestion& dnsQuestion); bool getEDNS0Record(const PacketBuffer& packet, EDNS0Record& edns0); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist-lua-bindings-dnsquestion.cc new/dnsdist-2.0.5/dnsdist-lua-bindings-dnsquestion.cc --- old/dnsdist-2.0.3/dnsdist-lua-bindings-dnsquestion.cc 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist-lua-bindings-dnsquestion.cc 2026-04-22 19:37:43.000000000 +0200 @@ -51,6 +51,21 @@ #endif /* DISABLE_PROTOBUF */ } +#ifndef DISABLE_NON_FFI_DQ_BINDINGS +static LuaArray<EDNSOptionValues> EDNSOptionViewsToValues(const EDNSOptionViewMap& ednsOptions) +{ + LuaArray<EDNSOptionValues> copy; + for (const auto& [code, views] : ednsOptions) { + EDNSOptionValues options; + for (const auto& value : views.values) { + options.values.emplace_back(value.content, value.size); + } + copy.emplace_back(code, std::move(options)); + } + return copy; +} +#endif /* DISABLE_NON_FFI_DQ_BINDINGS */ + // NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold void setupLuaBindingsDNSQuestion([[maybe_unused]] LuaContext& luaCtx) { @@ -162,15 +177,12 @@ return true; }); }); - luaCtx.registerFunction<std::map<uint16_t, EDNSOptionView> (DNSQuestion::*)() const>("getEDNSOptions", [](const DNSQuestion& dnsQuestion) { - if (dnsQuestion.ednsOptions == nullptr) { - parseEDNSOptions(dnsQuestion); - if (dnsQuestion.ednsOptions == nullptr) { - throw std::runtime_error("parseEDNSOptions should have populated the EDNS options"); - } + luaCtx.registerFunction<LuaArray<EDNSOptionValues> (DNSQuestion::*)() const>("getEDNSOptions", [](const DNSQuestion& dnsQuestion) -> LuaArray<EDNSOptionValues> { + auto ednsOptions = parseEDNSOptions(dnsQuestion); + if (!ednsOptions) { + return {}; } - - return *dnsQuestion.ednsOptions; + return EDNSOptionViewsToValues(*ednsOptions); }); luaCtx.registerFunction<std::string (DNSQuestion::*)(void) const>("getTrailingData", [](const DNSQuestion& dnsQuestion) { return dnsQuestion.getTrailingData(); @@ -477,15 +489,12 @@ }); }); - luaCtx.registerFunction<std::map<uint16_t, EDNSOptionView> (DNSResponse::*)() const>("getEDNSOptions", [](const DNSResponse& dnsQuestion) { - if (dnsQuestion.ednsOptions == nullptr) { - parseEDNSOptions(dnsQuestion); - if (dnsQuestion.ednsOptions == nullptr) { - throw std::runtime_error("parseEDNSOptions should have populated the EDNS options"); - } + luaCtx.registerFunction<LuaArray<EDNSOptionValues> (DNSResponse::*)() const>("getEDNSOptions", [](const DNSResponse& dnsQuestion) -> LuaArray<EDNSOptionValues> { + auto ednsOptions = parseEDNSOptions(dnsQuestion); + if (!ednsOptions) { + return {}; } - - return *dnsQuestion.ednsOptions; + return EDNSOptionViewsToValues(*ednsOptions); }); luaCtx.registerFunction<std::string (DNSResponse::*)(void) const>("getTrailingData", [](const DNSResponse& dnsQuestion) { return dnsQuestion.getTrailingData(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist-lua-bindings.cc new/dnsdist-2.0.5/dnsdist-lua-bindings.cc --- old/dnsdist-2.0.3/dnsdist-lua-bindings.cc 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist-lua-bindings.cc 2026-04-22 19:37:43.000000000 +0200 @@ -32,6 +32,7 @@ #include "dnsdist-xsk.hh" #include "dolog.hh" +#include "ednsoptions.hh" #include "xsk.hh" void setupLuaBindingsLogging(LuaContext& luaCtx) @@ -917,17 +918,12 @@ return xsk->getMetrics(); }); #endif /* HAVE_XSK */ - /* EDNSOptionView */ - luaCtx.registerFunction<size_t (EDNSOptionView::*)() const>("count", [](const EDNSOptionView& option) { - return option.values.size(); - }); - luaCtx.registerFunction<std::vector<string> (EDNSOptionView::*)() const>("getValues", [](const EDNSOptionView& option) { - std::vector<string> values; - values.reserve(values.size()); - for (const auto& value : option.values) { - values.emplace_back(value.content, value.size); - } - return values; + /* EDNSOptionValues */ + luaCtx.registerFunction<size_t (EDNSOptionValues::*)() const>("count", [](const EDNSOptionValues& values) { + return values.values.size(); + }); + luaCtx.registerFunction<std::vector<string> (EDNSOptionValues::*)() const>("getValues", [](const EDNSOptionValues& values) { + return values.values; }); luaCtx.writeFunction("newDOHResponseMapEntry", [](const std::string& regex, uint64_t status, const std::string& content, boost::optional<LuaAssociativeTable<std::string>> customHeaders) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist-lua-ffi.cc new/dnsdist-2.0.5/dnsdist-lua-ffi.cc --- old/dnsdist-2.0.3/dnsdist-lua-ffi.cc 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist-lua-ffi.cc 2026-04-22 19:37:43.000000000 +0200 @@ -393,16 +393,13 @@ // returns the length of the resulting 'out' array. 'out' is not set if the length is 0 size_t dnsdist_ffi_dnsquestion_get_edns_options(dnsdist_ffi_dnsquestion_t* dq, const dnsdist_ffi_ednsoption_t** out) { - if (dq->dq->ednsOptions == nullptr) { - parseEDNSOptions(*(dq->dq)); - - if (dq->dq->ednsOptions == nullptr) { - return 0; - } + auto ednsOptions = parseEDNSOptions(*(dq->dq)); + if (!ednsOptions) { + return 0; } size_t totalCount = 0; - for (const auto& option : *dq->dq->ednsOptions) { + for (const auto& option : *ednsOptions) { totalCount += option.second.values.size(); } @@ -412,7 +409,7 @@ dq->ednsOptionsVect->clear(); dq->ednsOptionsVect->resize(totalCount); size_t pos = 0; - for (const auto& option : *dq->dq->ednsOptions) { + for (const auto& option : *ednsOptions) { for (const auto& entry : option.second.values) { fill_edns_option(entry, dq->ednsOptionsVect->at(pos)); dq->ednsOptionsVect->at(pos).optionCode = option.first; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist-lua-inspection-ffi.cc new/dnsdist-2.0.5/dnsdist-lua-inspection-ffi.cc --- old/dnsdist-2.0.3/dnsdist-lua-inspection-ffi.cc 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist-lua-inspection-ffi.cc 2026-04-22 19:37:43.000000000 +0200 @@ -66,9 +66,11 @@ void dnsdist_ffi_stat_node_get_full_name_raw(const dnsdist_ffi_stat_node_t* node, const char** name, size_t* nameSize) { - const auto& storage = node->node.fullname; - *name = storage.c_str(); - *nameSize = storage.size(); + if (node->fullname.empty()) { + node->fullname = node->node.fullname.toString(); + } + *name = node->fullname.c_str(); + *nameSize = node->fullname.size(); } unsigned int dnsdist_ffi_stat_node_get_children_count(const dnsdist_ffi_stat_node_t* node) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist-lua-inspection.cc new/dnsdist-2.0.5/dnsdist-lua-inspection.cc --- old/dnsdist-2.0.3/dnsdist-lua-inspection.cc 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist-lua-inspection.cc 2026-04-22 19:37:43.000000000 +0200 @@ -902,7 +902,7 @@ [](const StatNode& node) -> unsigned int { return node.children.size(); }); - luaCtx.registerMember("fullname", &StatNode::fullname); + luaCtx.registerMember<std::string(StatNode::*)>(std::string("fullname"), [](const StatNode& node) -> std::string { return !node.fullname.empty() ? node.fullname.toString() : ""; }); luaCtx.registerMember("labelsCount", &StatNode::labelsCount); luaCtx.registerMember("servfails", &StatNode::Stat::servfails); luaCtx.registerMember("nxdomains", &StatNode::Stat::nxdomains); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist-nghttp2-in.cc new/dnsdist-2.0.5/dnsdist-nghttp2-in.cc --- old/dnsdist-2.0.3/dnsdist-nghttp2-in.cc 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist-nghttp2-in.cc 2026-04-22 19:37:43.000000000 +0200 @@ -269,6 +269,32 @@ sess = nullptr; } +static void addDateHeader(PacketBuffer& out) +{ + static const std::string dateformat("Date: %a, %d %h %Y %T GMT\r\n"); + using timebuf_t = std::array<char, 64>; + timebuf_t date_header{}; + struct tm tmval{}; + time_t timestamp = time(nullptr); + // apparently someone might be crazy enough to change the locale using Lua (why?) + // so we have to do extra work just in case + auto posixLocale = newlocale(LC_ALL_MASK, "POSIX", nullptr); + if (!posixLocale) { + return; + } + try { + size_t date_header_written = strftime_l(date_header.data(), date_header.size(), dateformat.data(), gmtime_r(×tamp, &tmval), posixLocale); + + if (date_header_written > 0 && date_header_written <= date_header.size()) { + out.insert(out.end(), date_header.begin(), date_header.begin() + date_header_written); + } + } + catch (...) { + } + + freelocale(posixLocale); +} + bool IncomingHTTP2Connection::checkALPN() { constexpr std::array<uint8_t, 2> h2ALPN{'h', '2'}; @@ -282,19 +308,13 @@ ++d_ci.cs->dohFrontend->d_http1Stats.d_nbQueries; } - static const std::string data0("HTTP/1.1 400 Bad Request\r\nConnection: Close\r\n"); + static const std::string data0("HTTP/1.1 505 HTTP Version Not Supported\r\nConnection: Close\r\n"); + d_out.insert(d_out.end(), data0.begin(), data0.end()); - std::array<char, 40> data1{}; - static const std::string dateformat("Date: %a, %d %h %Y %T GMT\r\n"); - struct tm tmval{}; - time_t timestamp = time(nullptr); - size_t len = strftime(data1.data(), data1.size(), dateformat.data(), gmtime_r(×tamp, &tmval)); - assert(len != 0); + addDateHeader(d_out); static const std::string data2("\r\n<html><body>This server implements RFC 8484 - DNS Queries over HTTP, and requires HTTP/2 in accordance with section 5.2 of the RFC.</body></html>\r\n"); - d_out.insert(d_out.end(), data0.begin(), data0.end()); - d_out.insert(d_out.end(), data1.begin(), data1.begin() + len); d_out.insert(d_out.end(), data2.begin(), data2.end()); writeToSocket(false); @@ -818,15 +838,16 @@ return true; } -static void processForwardedForHeader(const std::unique_ptr<HeadersMap>& headers, ComboAddress& remote) +static std::optional<ComboAddress> processForwardedForHeader(const std::unique_ptr<HeadersMap>& headers, const ComboAddress& remote) { + std::optional<ComboAddress> result{std::nullopt}; if (!headers) { - return; + return result; } auto headerIt = headers->find(s_xForwardedForHeaderName); if (headerIt == headers->end()) { - return; + return result; } std::string_view value = headerIt->second; @@ -841,8 +862,7 @@ value = value.substr(pos); } } - auto newRemote = ComboAddress(std::string(value)); - remote = newRemote; + result.emplace(std::string(value)); } catch (const std::exception& e) { vinfolog("Invalid X-Forwarded-For header ('%s') received from %s : %s", std::string(value), remote.toStringWithPort(), e.what()); @@ -850,6 +870,7 @@ catch (const PDNSException& e) { vinfolog("Invalid X-Forwarded-For header ('%s') received from %s : %s", std::string(value), remote.toStringWithPort(), e.reason); } + return result; } void IncomingHTTP2Connection::handleIncomingQuery(IncomingHTTP2Connection::PendingQuery&& query, IncomingHTTP2Connection::StreamID streamID) @@ -874,7 +895,13 @@ ++d_ci.cs->dohFrontend->d_http2Stats.d_nbQueries; if (d_ci.cs->dohFrontend->d_trustForwardedForHeader) { - processForwardedForHeader(query.d_headers, d_proxiedRemote); + auto xForwardedForRemote = processForwardedForHeader(query.d_headers, d_ci.remote); + if (xForwardedForRemote) { + d_proxiedRemote = *xForwardedForRemote; + } + else { + d_proxiedRemote = d_ci.remote; + } /* second ACL lookup based on the updated address */ if (!dnsdist::configuration::getCurrentRuntimeConfiguration().d_ACL.match(d_proxiedRemote)) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist-nghttp2.cc new/dnsdist-2.0.5/dnsdist-nghttp2.cc --- old/dnsdist-2.0.3/dnsdist-nghttp2.cc 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist-nghttp2.cc 2026-04-22 19:37:43.000000000 +0200 @@ -77,6 +77,10 @@ } private: + /* how many bytes we are willing to keep in a buffer waiting for the socket to become writable + again, until we stop accepting new queries */ + static constexpr size_t s_maxBufferedBytes = 65536U; + static ssize_t send_callback(nghttp2_session* session, const uint8_t* data, size_t length, int flags, void* user_data); static int on_frame_recv_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data); static int on_data_chunk_recv_callback(nghttp2_session* session, uint8_t flags, StreamID stream_id, const uint8_t* data, size_t len, void* user_data); @@ -223,7 +227,7 @@ bool DoHConnectionToBackend::reachedMaxStreamID() const { const uint32_t maximumStreamID = (static_cast<uint32_t>(1) << 31) - 1; - return d_highestStreamID == maximumStreamID; + return d_highestStreamID >= maximumStreamID; } bool DoHConnectionToBackend::reachedMaxConcurrentQueries() const @@ -232,6 +236,13 @@ if (nghttp2_session_get_remote_settings(d_session.get(), NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS) <= getConcurrentStreamsCount()) { return true; } + + /* somehow we already have a lot of data queued that we have not been able to + write to the outgoing socket, do not accept new queries just yet */ + if (d_out.size() >= s_maxBufferedBytes) { + return true; + } + return false; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist-rust-lib/rust/Cargo.lock new/dnsdist-2.0.5/dnsdist-rust-lib/rust/Cargo.lock --- old/dnsdist-2.0.3/dnsdist-rust-lib/rust/Cargo.lock 2026-03-13 16:13:21.684240600 +0100 +++ new/dnsdist-2.0.5/dnsdist-rust-lib/rust/Cargo.lock 2026-04-22 19:41:50.252330500 +0200 @@ -115,7 +115,7 @@ [[package]] name = "dnsdist-rust" -version = "2.0.3" +version = "2.0.5" dependencies = [ "cxx", "cxx-build", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist-rust-lib/rust/Cargo.toml new/dnsdist-2.0.5/dnsdist-rust-lib/rust/Cargo.toml --- old/dnsdist-2.0.3/dnsdist-rust-lib/rust/Cargo.toml 2026-03-13 16:13:21.143703700 +0100 +++ new/dnsdist-2.0.5/dnsdist-rust-lib/rust/Cargo.toml 2026-04-22 19:41:49.987798500 +0200 @@ -8,7 +8,7 @@ # BUILDER_VERSION when a release tarball is built # See builder-support/helpers/update-rust-library-version.py # called from meson-dist-script.sh -version = "2.0.3" +version = "2.0.5" [lib] name = "dnsdist_rust" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist-rust-lib/rust/src/lib.rs new/dnsdist-2.0.5/dnsdist-rust-lib/rust/src/lib.rs --- old/dnsdist-2.0.3/dnsdist-rust-lib/rust/src/lib.rs 2026-03-13 16:13:21.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist-rust-lib/rust/src/lib.rs 2026-04-22 19:41:49.000000000 +0200 @@ -1042,6 +1042,7 @@ struct QTypeSelectorConfiguration { #[serde(default, skip_serializing_if = "crate::is_default")] name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] qtype: String, #[serde(default, skip_serializing_if = "crate::is_default")] numeric_value: u16, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist-selectors-definitions.yml new/dnsdist-2.0.5/dnsdist-selectors-definitions.yml --- old/dnsdist-2.0.3/dnsdist-selectors-definitions.yml 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist-selectors-definitions.yml 2026-04-22 19:37:43.000000000 +0200 @@ -340,6 +340,7 @@ parameters: - name: "qtype" type: "String" + default: "" description: "The qtype, as a string" - name: "numeric_value" type: "u16" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist-tcp-downstream.cc new/dnsdist-2.0.5/dnsdist-tcp-downstream.cc --- old/dnsdist-2.0.3/dnsdist-tcp-downstream.cc 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist-tcp-downstream.cc 2026-04-22 19:37:43.000000000 +0200 @@ -892,6 +892,18 @@ return done; } +bool TCPConnectionToBackend::reachedMaxStreamID() const +{ + /* TCP/DoT has only 2^16 usable identifiers, DoH has 2^32 */ + const uint32_t maximumStreamID = std::numeric_limits<uint16_t>::max() - 1; + if (d_highestStreamID >= maximumStreamID) { + return true; + } + + /* pending queries will need IDs, so we need to take them into account as well */ + return (d_pendingQueries.size() >= (maximumStreamID - d_highestStreamID)); +} + void setTCPDownstreamMaxIdleConnectionsPerBackend(uint64_t max) { DownstreamTCPConnectionsManager::setMaxIdleConnectionsPerDownstream(max); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist-tcp-downstream.hh new/dnsdist-2.0.5/dnsdist-tcp-downstream.hh --- old/dnsdist-2.0.3/dnsdist-tcp-downstream.hh 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist-tcp-downstream.hh 2026-04-22 19:37:43.000000000 +0200 @@ -237,12 +237,7 @@ return d_state == State::idle && d_pendingQueries.size() == 0 && d_pendingResponses.size() == 0; } - bool reachedMaxStreamID() const override - { - /* TCP/DoT has only 2^16 usable identifiers, DoH has 2^32 */ - const uint32_t maximumStreamID = std::numeric_limits<uint16_t>::max() - 1; - return d_highestStreamID == maximumStreamID; - } + bool reachedMaxStreamID() const override; bool reachedMaxConcurrentQueries() const override { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist.1 new/dnsdist-2.0.5/dnsdist.1 --- old/dnsdist-2.0.3/dnsdist.1 2026-03-13 16:13:51.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist.1 2026-04-22 19:42:15.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" "Mar 13, 2026" "" "dnsdist" +.TH "DNSDIST" "1" "Apr 22, 2026" "" "dnsdist" .SH NAME dnsdist \- A DNS and DoS aware, scriptable loadbalancer .SH SYNOPSIS diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist.cc new/dnsdist-2.0.5/dnsdist.cc --- old/dnsdist-2.0.3/dnsdist.cc 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist.cc 2026-04-22 19:37:43.000000000 +0200 @@ -1236,8 +1236,8 @@ if ((msgh->msg_flags & MSG_TRUNC) != 0) { /* message was too large for our buffer */ vinfolog("Dropping message too large for our buffer"); - ++clientState.nonCompliantQueries; ++dnsdist::metrics::g_stats.nonCompliantQueries; + ++clientState.nonCompliantQueries; return false; } @@ -2135,7 +2135,7 @@ /* block until we have at least one message ready, but return as many as possible to save the syscall costs */ - msgsGot = recvmmsg(clientState->udpFD, msgVec.data(), vectSize, MSG_WAITFORONE | MSG_TRUNC, nullptr); + msgsGot = recvmmsg(clientState->udpFD, msgVec.data(), vectSize, MSG_WAITFORONE, nullptr); if (msgsGot <= 0) { vinfolog("Getting UDP messages via recvmmsg() failed with: %s", stringerror()); msgsGot = 0; @@ -2157,6 +2157,13 @@ continue; } + if ((msgh->msg_flags & MSG_TRUNC) != 0) { + /* message was too large for our buffer */ + ++dnsdist::metrics::g_stats.nonCompliantQueries; + ++clientState->nonCompliantQueries; + continue; + } + auto& data = recvData[msgIdx]; data.packet.resize(got); dnsdist::configuration::refreshLocalRuntimeConfiguration(); @@ -3345,9 +3352,9 @@ std::thread udpThreadHandle(udpClientThread, udpStates); udpThreadHandle.detach(); } - if (!tcpStates.empty()) { - g_tcpclientthreads = std::make_unique<TCPClientCollection>(1, tcpStates); - } + + /* Gives TCP client threads by default */ + g_tcpclientthreads = std::make_unique<TCPClientCollection>(1, tcpStates); #endif /* USE_SINGLE_ACCEPTOR_THREAD */ } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsdist.hh new/dnsdist-2.0.5/dnsdist.hh --- old/dnsdist-2.0.3/dnsdist.hh 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/dnsdist.hh 2026-04-22 19:37:43.000000000 +0200 @@ -42,7 +42,6 @@ #include "dnsdist-doh-common.hh" #include "doq.hh" #include "doh3.hh" -#include "ednsoptions.hh" #include "iputils.hh" #include "misc.hh" #include "mplexer.hh" @@ -77,7 +76,6 @@ } PacketBuffer& getMutableData() { - ednsOptions.reset(); return data; } @@ -176,7 +174,6 @@ InternalQueryState& ids; std::unique_ptr<Netmask> ecs{nullptr}; std::string sni; /* Server Name Indication, if any (DoT or DoH) */ - mutable std::unique_ptr<EDNSOptionViewMap> ednsOptions; /* this needs to be mutable because it is parsed just in time, when DNSQuestion is read-only */ std::shared_ptr<IncomingTCPConnectionState> d_incomingTCPState{nullptr}; std::unique_ptr<std::vector<ProxyProtocolValue>> proxyProtocolValues{nullptr}; uint16_t ecsPrefixLength; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/dnsparser.cc new/dnsdist-2.0.5/dnsparser.cc --- old/dnsdist-2.0.3/dnsparser.cc 2026-03-13 16:12:38.000000000 +0100 +++ new/dnsdist-2.0.5/dnsparser.cc 2026-04-22 19:40:55.000000000 +0200 @@ -1235,13 +1235,12 @@ uint32_t dnsttl = reader.get32BitInt(); uint16_t contentLength = reader.get16BitInt(); uint16_t pos = reader.getPosition(); + reader.skip(contentLength); bool done = visitor(section, dnsclass, dnstype, dnsttl, contentLength, &packet.at(pos)); if (done) { return true; } - - reader.skip(contentLength); } } catch (...) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/docs/reference/yaml-selectors.rst new/dnsdist-2.0.5/docs/reference/yaml-selectors.rst --- old/dnsdist-2.0.3/docs/reference/yaml-selectors.rst 2026-03-13 16:13:18.000000000 +0100 +++ new/dnsdist-2.0.5/docs/reference/yaml-selectors.rst 2026-04-22 19:41:48.000000000 +0200 @@ -514,7 +514,7 @@ Parameters: -- **qtype**: String - The qtype, as a string +- **qtype**: String ``("")`` - The qtype, as a string - **numeric_value**: Unsigned integer ``(0)`` - The qtype, as a numerical value diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/doh3.cc new/dnsdist-2.0.5/doh3.cc --- old/dnsdist-2.0.3/doh3.cc 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/doh3.cc 2026-04-22 19:37:43.000000000 +0200 @@ -30,13 +30,12 @@ #include "misc.hh" #include "sstuff.hh" #include "threadname.hh" -#include "base64.hh" +#include "dnsdist-concurrent-connections.hh" #include "dnsdist-dnsparser.hh" #include "dnsdist-ecs.hh" #include "dnsdist-proxy-protocol.hh" #include "dnsdist-tcp.hh" -#include "dnsdist-random.hh" #include "doq-common.hh" @@ -60,7 +59,18 @@ H3Connection(H3Connection&&) = default; H3Connection& operator=(const H3Connection&) = delete; H3Connection& operator=(H3Connection&&) = default; - ~H3Connection() = default; + ~H3Connection() + { + try { + /* do not account if we have been moved! */ + if (d_conn) { + dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(d_peer); + } + } + catch (...) { + /* in theory it might raise an exception, and we cannot allow it to be uncaught in a dtor */ + } + } std::shared_ptr<const std::string> getSNI() { @@ -638,10 +648,13 @@ if (downstream->passCrossProtocolQuery(std::move(cpq))) { return; } - // NOLINTNEXTLINE(bugprone-use-after-move): it was only moved if the call succeeded - unit = cpq->releaseDU(); - unit->status_code = 500; - handleImmediateResponse(std::move(unit), "DoH3 internal error"); + + /* On exceptional cases, cpq is moved but returns false above. So we check to make sure. See https://github.com/PowerDNS/pdns/issues/17109 */ + if (cpq) { + unit = cpq->releaseDU(); + unit->status_code = 500; + handleImmediateResponse(std::move(unit), "DoH3 internal error"); + } return; } catch (const std::exception& e) { @@ -719,6 +732,8 @@ ++clientState.nonCompliantQueries; ++frontend.d_errorResponses; h3_send_response(conn, streamID, 400, msg); + conn.d_streamBuffers.erase(streamID); + conn.d_headersBuffers.erase(streamID); }; auto& headers = conn.d_headersBuffers.at(streamID); @@ -876,8 +891,11 @@ } case QUICHE_H3_EVENT_FINISHED: case QUICHE_H3_EVENT_RESET: - case QUICHE_H3_EVENT_PRIORITY_UPDATE: + conn.d_headersBuffers.erase(streamID); + conn.d_streamBuffers.erase(streamID); + break; case QUICHE_H3_EVENT_GOAWAY: + case QUICHE_H3_EVENT_PRIORITY_UPDATE: break; } } @@ -962,6 +980,12 @@ continue; } + auto connectionResult = dnsdist::IncomingConcurrentTCPConnectionsManager::accountNewTCPConnection(client, true, true); + if (connectionResult == dnsdist::IncomingConcurrentTCPConnectionsManager::NewConnectionResult::Denied) { + DEBUGLOG("Connection not allowed!"); + continue; + } + DEBUGLOG("Creating a new connection"); conn = createConnection(*frontend.d_server_config, serverConnID, *originalDestinationID, localAddr, client); if (!conn) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/doq.cc new/dnsdist-2.0.5/doq.cc --- old/dnsdist-2.0.3/doq.cc 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/doq.cc 2026-04-22 19:37:43.000000000 +0200 @@ -31,11 +31,11 @@ #include "sstuff.hh" #include "threadname.hh" +#include "dnsdist-concurrent-connections.hh" #include "dnsdist-dnsparser.hh" #include "dnsdist-ecs.hh" #include "dnsdist-proxy-protocol.hh" #include "dnsdist-tcp.hh" -#include "dnsdist-random.hh" #include "doq-common.hh" @@ -59,7 +59,18 @@ Connection(Connection&&) = default; Connection& operator=(const Connection&) = delete; Connection& operator=(Connection&&) = default; - ~Connection() = default; + ~Connection() + { + try { + /* do not account if we have been moved! */ + if (d_conn) { + dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(d_peer); + } + } + catch (...) { + /* in theory it might raise an exception, and we cannot allow it to be uncaught in a dtor */ + } + } std::shared_ptr<const std::string> getSNI() { @@ -541,9 +552,11 @@ if (downstream->passCrossProtocolQuery(std::move(cpq))) { return; } - // NOLINTNEXTLINE(bugprone-use-after-move): it was only moved if the call succeeded - unit = cpq->releaseDU(); - handleImmediateResponse(std::move(unit), "DoQ internal error"); + /* On exceptional cases, cpq is moved but returns false above. So we check to make sure. See https://github.com/PowerDNS/pdns/issues/17109 */ + if (cpq) { + unit = cpq->releaseDU(); + handleImmediateResponse(std::move(unit), "DoQ internal error"); + } return; } catch (const std::exception& e) { @@ -637,6 +650,7 @@ ++dnsdist::metrics::g_stats.nonCompliantQueries; ++clientState.nonCompliantQueries; quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(DOQ_Error_Codes::DOQ_PROTOCOL_ERROR)); + conn.d_streamBuffers.erase(streamID); return; } @@ -659,6 +673,7 @@ ++dnsdist::metrics::g_stats.nonCompliantQueries; ++clientState.nonCompliantQueries; quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(DOQ_Error_Codes::DOQ_PROTOCOL_ERROR)); + conn.d_streamBuffers.erase(streamID); return; } @@ -668,6 +683,7 @@ ++dnsdist::metrics::g_stats.nonCompliantQueries; ++clientState.nonCompliantQueries; quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(DOQ_Error_Codes::DOQ_PROTOCOL_ERROR)); + conn.d_streamBuffers.erase(streamID); return; } DEBUGLOG("Dispatching query"); @@ -753,6 +769,12 @@ continue; } + auto connectionResult = dnsdist::IncomingConcurrentTCPConnectionsManager::accountNewTCPConnection(client, true, true); + if (connectionResult == dnsdist::IncomingConcurrentTCPConnectionsManager::NewConnectionResult::Denied) { + DEBUGLOG("Connection not allowed!"); + continue; + } + DEBUGLOG("Creating a new connection"); conn = createConnection(*frontend.d_server_config, serverConnID, *originalDestinationID, client, localAddr); if (!conn) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/ednsoptions.hh new/dnsdist-2.0.5/ednsoptions.hh --- old/dnsdist-2.0.3/ednsoptions.hh 2026-03-13 16:12:38.000000000 +0100 +++ new/dnsdist-2.0.5/ednsoptions.hh 2026-04-22 19:40:55.000000000 +0200 @@ -20,7 +20,9 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once -#include "namespaces.hh" +#include <cstdint> +#include <map> +#include <vector> struct EDNSOptionCode { @@ -41,6 +43,11 @@ std::vector<EDNSOptionViewValue> values; }; +struct EDNSOptionValues +{ + std::vector<std::string> values; +}; + static constexpr size_t EDNSOptionCodeSize = 2; static constexpr size_t EDNSOptionLengthSize = 2; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/ext/yahttp/yahttp/reqresp.cpp new/dnsdist-2.0.5/ext/yahttp/yahttp/reqresp.cpp --- old/dnsdist-2.0.3/ext/yahttp/yahttp/reqresp.cpp 2026-03-13 16:12:38.000000000 +0100 +++ new/dnsdist-2.0.5/ext/yahttp/yahttp/reqresp.cpp 2026-04-22 19:40:55.000000000 +0200 @@ -40,7 +40,19 @@ } template <class T> - bool AsyncLoader<T>::feed(const std::string& somedata) { + bool AsyncLoader<T>::feed(const std::string& somedata) + { + if (state < 2) { + headersize += somedata.length(); // maye include some body data, we don't know yet... + if (headersize > target->max_header_size) { + if (target->kind == YAHTTP_TYPE_REQUEST) { + throw ParseError("Request header too large"); + } + else { + throw ParseError("Response header too large"); + } + } + } buffer.append(somedata); while(state < 2) { int cr=0; @@ -155,8 +167,8 @@ maxbody = minbody; } if (minbody < 1) return true; // guess there isn't anything left. - if (target->kind == YAHTTP_TYPE_REQUEST && static_cast<ssize_t>(minbody) > target->max_request_size) throw ParseError("Max request body size exceeded"); - else if (target->kind == YAHTTP_TYPE_RESPONSE && static_cast<ssize_t>(minbody) > target->max_response_size) throw ParseError("Max response body size exceeded"); + if (target->kind == YAHTTP_TYPE_REQUEST && minbody > target->max_request_size) throw ParseError("Max request body size exceeded"); + else if (target->kind == YAHTTP_TYPE_RESPONSE && minbody > target->max_response_size) throw ParseError("Max response body size exceeded"); } if (maxbody == 0) hasBody = false; @@ -175,20 +187,23 @@ buffer.copy(buf, pos); buf[pos]=0; // just in case... buffer.erase(buffer.begin(), buffer.begin()+pos+1); // remove line from buffer - if (sscanf(buf, "%x", &chunk_size) != 1) { + if (sscanf(buf, "%zx", &chunk_size) != 1) { throw ParseError("Unable to parse chunk size"); } if (chunk_size == 0) { state = 3; break; } // last chunk - if (chunk_size > (std::numeric_limits<decltype(chunk_size)>::max() - 2)) { + if (chunk_size > (std::numeric_limits<decltype(chunk_size)>::max() - 2) || chunk_size > maxbody) { throw ParseError("Chunk is too large"); } } else { int crlf=1; - if (buffer.size() < static_cast<size_t>(chunk_size+1)) return false; // expect newline + if (buffer.size() < chunk_size+1) return false; // expect newline if (buffer.at(chunk_size) == '\r') { - if (buffer.size() < static_cast<size_t>(chunk_size+2) || buffer.at(chunk_size+1) != '\n') return false; // expect newline after carriage return + if (buffer.size() < chunk_size+2 || buffer.at(chunk_size+1) != '\n') return false; // expect newline after carriage return crlf=2; } else if (buffer.at(chunk_size) != '\n') return false; + if (bodybuf.str().length() + chunk_size > maxbody) { + throw ParseError("Chunked body is too large"); + } std::string tmp = buffer.substr(0, chunk_size); buffer.erase(buffer.begin(), buffer.begin()+chunk_size+crlf); bodybuf << tmp; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/ext/yahttp/yahttp/reqresp.hpp new/dnsdist-2.0.5/ext/yahttp/yahttp/reqresp.hpp --- old/dnsdist-2.0.3/ext/yahttp/yahttp/reqresp.hpp 2026-03-13 16:12:38.000000000 +0100 +++ new/dnsdist-2.0.5/ext/yahttp/yahttp/reqresp.hpp 2026-04-22 19:40:55.000000000 +0200 @@ -20,6 +20,10 @@ #include <algorithm> +#ifndef YAHTTP_MAX_HEADER_SIZE +#define YAHTTP_MAX_HEADER_SIZE (100 * 1024) +#endif + #ifndef YAHTTP_MAX_REQUEST_SIZE #define YAHTTP_MAX_REQUEST_SIZE 2097152 #endif @@ -108,6 +112,7 @@ #endif max_request_size = YAHTTP_MAX_REQUEST_SIZE; max_response_size = YAHTTP_MAX_RESPONSE_SIZE; + max_header_size = YAHTTP_MAX_HEADER_SIZE; url = ""; method = ""; statusText = ""; @@ -130,6 +135,7 @@ this->parameters = rhs.parameters; this->getvars = rhs.getvars; this->body = rhs.body; this->max_request_size = rhs.max_request_size; this->max_response_size = rhs.max_response_size; this->version = rhs.version; + this->max_header_size = rhs.max_header_size; #ifdef HAVE_CPP_FUNC_PTR this->renderer = rhs.renderer; #endif @@ -143,6 +149,7 @@ this->parameters = rhs.parameters; this->getvars = rhs.getvars; this->body = rhs.body; this->max_request_size = rhs.max_request_size; this->max_response_size = rhs.max_response_size; this->version = rhs.version; + this->max_header_size = rhs.max_header_size; #ifdef HAVE_CPP_FUNC_PTR this->renderer = rhs.renderer; #endif @@ -166,8 +173,9 @@ std::string body; //<! the actual content - ssize_t max_request_size; //<! maximum size of request - ssize_t max_response_size; //<! maximum size of response + size_t max_request_size; //<! maximum size of request + size_t max_response_size; //<! maximum size of response + size_t max_header_size; //<! maximum size of headers bool is_multipart; //<! if the request is multipart, prevents Content-Length header #ifdef HAVE_CPP_FUNC_PTR funcptr::function<size_t(const HTTPBase*,std::ostream&,bool)> renderer; //<! rendering function @@ -301,10 +309,11 @@ std::string buffer; //<! read buffer bool chunked; //<! whether we are parsing chunked data - int chunk_size; //<! expected size of next chunk + size_t chunk_size; //<! expected size of next chunk std::ostringstream bodybuf; //<! buffer for body size_t maxbody; //<! maximum size of body size_t minbody; //<! minimum size of body + size_t headersize; bool hasBody; //<! are we expecting body void keyValuePair(const std::string &keyvalue, std::string &key, std::string &value); //<! key value pair parser helper @@ -315,6 +324,7 @@ pos = 0; state = 0; this->target = target_; hasBody = false; buffer = ""; + headersize = 0; this->target->initialize(); }; //<! Initialize the parser for target and clear state bool feed(const std::string& somedata); //<! Feed data to the parser diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/meson/gnutls/meson.build new/dnsdist-2.0.5/meson/gnutls/meson.build --- old/dnsdist-2.0.3/meson/gnutls/meson.build 2026-03-13 16:12:38.000000000 +0100 +++ new/dnsdist-2.0.5/meson/gnutls/meson.build 2026-04-22 19:40:55.000000000 +0200 @@ -7,6 +7,7 @@ 'gnutls_session_set_verify_cert', 'gnutls_session_get_verify_cert_status', 'gnutls_alpn_set_protocols', + 'gnutls_transport_set_fastopen', ] foreach func: funcs diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/meson/libssl/meson.build new/dnsdist-2.0.5/meson/libssl/meson.build --- old/dnsdist-2.0.3/meson/libssl/meson.build 2026-03-13 16:12:38.000000000 +0100 +++ new/dnsdist-2.0.5/meson/libssl/meson.build 2026-04-22 19:40:55.000000000 +0200 @@ -25,6 +25,7 @@ 'SSL_get0_next_proto_negotiated', 'SSL_CTX_set_alpn_select_cb', 'SSL_CTX_use_cert_and_key', + 'TLS_client_method', ] foreach func: funcs diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/meson.build new/dnsdist-2.0.5/meson.build --- old/dnsdist-2.0.3/meson.build 2026-03-13 16:13:52.820979400 +0100 +++ new/dnsdist-2.0.5/meson.build 2026-04-22 19:42:16.696439000 +0200 @@ -1,7 +1,7 @@ project( 'dnsdist', ['c', 'cpp'], -version : '2.0.3', +version : '2.0.5', license : 'GPLv2', license_files : 'NOTICE', meson_version : '>= 1.3.0', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/statnode.cc new/dnsdist-2.0.5/statnode.cc --- old/dnsdist-2.0.3/statnode.cc 2026-03-13 16:12:38.000000000 +0100 +++ new/dnsdist-2.0.5/statnode.cc 2026-04-22 19:40:55.000000000 +0200 @@ -22,10 +22,10 @@ childstat=child.second.print(depth+8, childstat, silent || children.size()>1024); } if(!silent || children.size()>1) - cout<<string(depth, ' ')<<childstat.queries<<" queries, " << - childstat.noerrors<<" noerrors, "<< - childstat.nxdomains<<" nxdomains, "<< - childstat.servfails<<" servfails, "<< + cout<<string(depth, ' ')<<childstat.queries<<" queries, " << + childstat.noerrors<<" noerrors, "<< + childstat.nxdomains<<" nxdomains, "<< + childstat.servfails<<" servfails, "<< childstat.drops<<" drops, "<< childstat.bytes<<" bytes, "<< childstat.hits<<" hits"<<endl; @@ -57,20 +57,20 @@ } auto last = tmp.end() - 1; - children[*last].submit(last, tmp.begin(), "", rcode, bytes, remote, 1, hit); + children[*last].submit(last, tmp.begin(), g_rootdnsname, rcode, bytes, remote, 1, hit); } -/* www.powerdns.com. -> +/* www.powerdns.com. -> . <- fullnames com. powerdns.com - www.powerdns.com. + www.powerdns.com. */ -void StatNode::submit(std::vector<string>::const_iterator end, std::vector<string>::const_iterator begin, const std::string& domain, int rcode, unsigned int bytes, const std::optional<ComboAddress>& remote, unsigned int count, bool hit) +void StatNode::submit(std::vector<string>::const_iterator end, std::vector<string>::const_iterator begin, const DNSName& domain, int rcode, unsigned int bytes, const std::optional<ComboAddress>& remote, unsigned int count, bool hit) { // cerr<<"Submit called for domain='"<<domain<<"': "; - // for(const std::string& n : labels) + // for(const std::string& n : labels) // cerr<<n<<"."; // cerr<<endl; if (name.empty()) { @@ -84,13 +84,8 @@ if (end == begin) { if (fullname.empty()) { - size_t needed = name.size() + 1 + domain.size(); - if (fullname.capacity() < needed) { - fullname.reserve(needed); - } - fullname = name; - fullname.append("."); - fullname.append(domain); + fullname = domain; + fullname.prependRawLabel(name); labelsCount = count; } // cerr<<"Hit the end, set our fullname to '"<<fullname<<"'"<<endl<<endl; @@ -119,13 +114,8 @@ } else { if (fullname.empty()) { - size_t needed = name.size() + 1 + domain.size(); - if (fullname.capacity() < needed) { - fullname.reserve(needed); - } - fullname = name; - fullname.append("."); - fullname.append(domain); + fullname = domain; + fullname.prependRawLabel(name); labelsCount = count; } // cerr<<"Not yet end, set our fullname to '"<<fullname<<"', recursing"<<endl; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/statnode.hh new/dnsdist-2.0.5/statnode.hh --- old/dnsdist-2.0.3/statnode.hh 2026-03-13 16:12:38.000000000 +0100 +++ new/dnsdist-2.0.5/statnode.hh 2026-04-22 19:40:55.000000000 +0200 @@ -62,7 +62,7 @@ Stat s; std::string name; - std::string fullname; + DNSName fullname; uint8_t labelsCount{0}; void submit(const DNSName& domain, int rcode, unsigned int bytes, bool hit, const std::optional<ComboAddress>& remote); @@ -75,5 +75,5 @@ children_t children; private: - void submit(std::vector<string>::const_iterator end, std::vector<string>::const_iterator begin, const std::string& domain, int rcode, unsigned int bytes, const std::optional<ComboAddress>& remote, unsigned int count, bool hit); + void submit(std::vector<string>::const_iterator end, std::vector<string>::const_iterator begin, const DNSName& domain, int rcode, unsigned int bytes, const std::optional<ComboAddress>& remote, unsigned int count, bool hit); }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/tcpiohandler.cc new/dnsdist-2.0.5/tcpiohandler.cc --- old/dnsdist-2.0.3/tcpiohandler.cc 2026-03-13 16:12:38.000000000 +0100 +++ new/dnsdist-2.0.5/tcpiohandler.cc 2026-04-22 19:40:55.000000000 +0200 @@ -1003,6 +1003,7 @@ #ifdef HAVE_GNUTLS #include <gnutls/gnutls.h> +#include <gnutls/socket.h> #include <gnutls/x509.h> static void safe_memory_lock([[maybe_unused]] void* data, [[maybe_unused]] size_t size) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdist-2.0.3/test-dnsdist_cc.cc new/dnsdist-2.0.5/test-dnsdist_cc.cc --- old/dnsdist-2.0.3/test-dnsdist_cc.cc 2026-03-12 16:00:00.000000000 +0100 +++ new/dnsdist-2.0.5/test-dnsdist_cc.cc 2026-04-22 19:37:43.000000000 +0200 @@ -158,11 +158,11 @@ ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) DNSQuestion dnsQuestion(ids, const_cast<PacketBuffer&>(packet)); - BOOST_CHECK(parseEDNSOptions(dnsQuestion)); - BOOST_REQUIRE(dnsQuestion.ednsOptions != nullptr); - BOOST_CHECK_EQUAL(dnsQuestion.ednsOptions->size(), 1U); - const auto& ecsOption = dnsQuestion.ednsOptions->find(EDNSOptionCode::ECS); - BOOST_REQUIRE(ecsOption != dnsQuestion.ednsOptions->cend()); + auto ednsOptions = parseEDNSOptions(dnsQuestion); + BOOST_REQUIRE(ednsOptions); + BOOST_CHECK_EQUAL(ednsOptions->size(), 1U); + const auto& ecsOption = ednsOptions->find(EDNSOptionCode::ECS); + BOOST_REQUIRE(ecsOption != ednsOptions->cend()); string expectedOption; generateECSOption(expected, expectedOption, expected.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6); @@ -2460,11 +2460,11 @@ BOOST_CHECK_EQUAL(edns0.extRCode, 0U); BOOST_CHECK_EQUAL(ntohs(edns0.extFlags), EDNS_HEADER_FLAG_DO); - BOOST_REQUIRE(parseEDNSOptions(dnsQuestion)); - BOOST_REQUIRE(dnsQuestion.ednsOptions != nullptr); - BOOST_CHECK_EQUAL(dnsQuestion.ednsOptions->size(), 1U); - const auto& ecsOption = dnsQuestion.ednsOptions->find(EDNSOptionCode::COOKIE); - BOOST_REQUIRE(ecsOption != dnsQuestion.ednsOptions->cend()); + auto ednsOptions = parseEDNSOptions(dnsQuestion); + BOOST_REQUIRE(ednsOptions); + BOOST_CHECK_EQUAL(ednsOptions->size(), 1U); + const auto& ecsOption = ednsOptions->find(EDNSOptionCode::COOKIE); + BOOST_REQUIRE(ecsOption != ednsOptions->cend()); BOOST_REQUIRE_EQUAL(ecsOption->second.values.size(), 1U); BOOST_CHECK_EQUAL(cookiesOptionStr, std::string(ecsOption->second.values.at(0).content, ecsOption->second.values.at(0).size));
