commit: 93effd99eb26d890c9cefa0e8bcbaf300c7ca293 Author: Holger Hoffstätte <holger <AT> applied-asynchrony <DOT> com> AuthorDate: Tue Dec 2 13:18:21 2025 +0000 Commit: Sam James <sam <AT> gentoo <DOT> org> CommitDate: Thu Dec 4 12:16:23 2025 +0000 URL: https://gitweb.gentoo.org/repo/gentoo.git/commit/?id=93effd99
net-dns/dnsdist: add 2.0.2 - migrated build to meson - upstream contributions - backported performance fixes Signed-off-by: Holger Hoffstätte <holger <AT> applied-asynchrony.com> Part-of: https://github.com/gentoo/gentoo/pull/44863 Signed-off-by: Sam James <sam <AT> gentoo.org> net-dns/dnsdist/Manifest | 3 + net-dns/dnsdist/dnsdist-2.0.2.ebuild | 184 ++++++++++++ .../dnsdist/files/2.0.2-roundrobin-fast-path.patch | 39 +++ .../dnsdist/files/2.0.2-speed-up-cache-hits.patch | 327 +++++++++++++++++++++ 4 files changed, 553 insertions(+) diff --git a/net-dns/dnsdist/Manifest b/net-dns/dnsdist/Manifest index 1deff8da9c1c..ba900c844c90 100644 --- a/net-dns/dnsdist/Manifest +++ b/net-dns/dnsdist/Manifest @@ -1,5 +1,8 @@ DIST dnsdist-1.9.10.tar.bz2 1598472 BLAKE2B ea66ca17ef66ecc64fd3a7379b22c2b0448c2a41f325e574a4edb20dfe408315be84a407b78f30a441479fbbcba31a28da2e310c275877739918ad3f9870acd1 SHA512 d7249861bb5454dce3d179701e1c686c5c5ed177ca39b07ca6b1f27d2ab7a014d0d255ee6b70153962dc5d9a84545ae4a4a55c53c8e75f308cda5406eed57e9b DIST dnsdist-1.9.11.tar.bz2 1598511 BLAKE2B 54b197e625e10aa84238264e33b8df398d151645883586c778669741f96f21aee8b2242cec593e9ed2db19a134600cceb5eb69c193a1e527b6da4025b9658c73 SHA512 d1460051e4cc30c4df48f640dc18846ea68102227df3ef016cdb63c8ef62151ee99748c370dc3084aa06b9e2c902a9991db8d2134cded71dac18b1271d1bd2db DIST dnsdist-2.0.1.tar.xz 2279512 BLAKE2B 6eee67a678ef1a044f60f8989befdcf84ce487bcbe03d2aedbb196b1393f7b5227e93ca25a56e4c400c4159e6e7ec1474e26311ae76c55116f438de234b724d7 SHA512 8e0b6b9d9db36e19c4617e79a36f86f8bc1a0096569dab0dc178ee9fa1b3af3b8baaa40252be9c7450a01e2d169a530edcc8f52e794e4efa649f5f0582b579d1 +DIST dnsdist-2.0.2.tar.xz 2284864 BLAKE2B 87a4179f474d7f8117e4e03c4ff680f79207c8056765c6925ee21b3011e4555e47ad9c97950f30ab2876ac0978ee07555017cb1bbc6b93792992c1821f8da27a SHA512 7f53d13bb90b7b70da364341e50473b88be0bc9619e3263e352bed75aa57edbc018824439749956281a2c7a5d32c653e7378fe9d3cbc296042fa8120eee75fae DIST dnsdist-docs-2.0.1.tar.xz 1382776 BLAKE2B 9b701e1c06118f242cf5fef019c015d5bd9608db893c1e708666d572cc8d0b3745f5bd935e76995e2894288a3d039aea9de6a409295ba096a72acd960d4e5507 SHA512 fc0ce707ec6dddf0057f8fd4bafe7513017b8e71e39b77e0525a67fbab68bbc1393490368eacad31ffccebebed156d0cc6ec646dbba30369ff67c63d71410057 +DIST dnsdist-docs-2.0.2.tar.xz 1385828 BLAKE2B 79b4bb02f42f9bb3829fec6c6788ea2cc79c3f55d31b462e3c54d32f8afb0dfc84d8b347cbdae6cc3f9d8c9ebd1207a71f097f5201f14b9b2b6b20c72842d6b2 SHA512 ab73d30193bd99b4163962be5564c596fdd1d0c85c20d6606a70ed491ceb8ff801418aa7890c9ad2025ef3c9767f1d8252c0c18cfee5ebf9d845cce7e681f578 DIST dnsdist-rust-2.0.1-crates.tar.xz 5268364 BLAKE2B 36b370eacb7332d04de0fa1be2a49983c030c94df00abcc6681180b1ad5fa8bfe3e19744543b3707e305d57649837a632c1400fd4ae1b4626f084310db8f9bad SHA512 a9f1e10a71a9fb2e6879077224823dd278f1a27c5698076e69bc4bcdf89cff5e54ffd0fce90113131358398289400da263b90c895d167cb3c007fcc96654e116 +DIST dnsdist-rust-2.0.2-crates.tar.xz 5268364 BLAKE2B 36b370eacb7332d04de0fa1be2a49983c030c94df00abcc6681180b1ad5fa8bfe3e19744543b3707e305d57649837a632c1400fd4ae1b4626f084310db8f9bad SHA512 a9f1e10a71a9fb2e6879077224823dd278f1a27c5698076e69bc4bcdf89cff5e54ffd0fce90113131358398289400da263b90c895d167cb3c007fcc96654e116 diff --git a/net-dns/dnsdist/dnsdist-2.0.2.ebuild b/net-dns/dnsdist/dnsdist-2.0.2.ebuild new file mode 100644 index 000000000000..f5bb9aebd801 --- /dev/null +++ b/net-dns/dnsdist/dnsdist-2.0.2.ebuild @@ -0,0 +1,184 @@ +# Copyright 1999-2025 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +EAPI=8 + +LUA_COMPAT=( lua5-{1..4} luajit ) +PYTHON_COMPAT=( python3_{11..14} ) +RUST_MIN_VER="1.85.1" +RUST_OPTIONAL=1 + +inherit cargo flag-o-matic lua-single meson python-any-r1 toolchain-funcs + +DESCRIPTION="A highly DNS-, DoS- and abuse-aware loadbalancer" +HOMEPAGE="https://www.dnsdist.org/index.html" + +if [[ ${PV} == *9999* ]] ; then + EGIT_REPO_URI="https://github.com/PowerDNS/pdns" + EGIT_BRANCH="master" + inherit git-r3 +else + SRC_URI="https://downloads.powerdns.com/releases/${P}.tar.xz" + KEYWORDS="~amd64 ~x86" +fi + +SRC_URI+=" + doc? ( https://www.applied-asynchrony.com/distfiles/${PN}-docs-${PV}.tar.xz ) + yaml? ( https://www.applied-asynchrony.com/distfiles/${PN}-rust-${PV}-crates.tar.xz ) +" + +LICENSE="GPL-2" +SLOT="0" +IUSE="bpf cdb dnscrypt dnstap doc doh doh3 ipcipher lmdb quic regex snmp +ssl systemd test web xdp yaml" +RESTRICT="!test? ( test )" + +REQUIRED_USE="${LUA_REQUIRED_USE} + dnscrypt? ( ssl ) + doh? ( ssl ) + doh3? ( ssl quic ) + ipcipher? ( ssl ) + quic? ( ssl )" + +RDEPEND="acct-group/dnsdist + acct-user/dnsdist + bpf? ( dev-libs/libbpf:= ) + cdb? ( dev-db/tinycdb:= ) + dev-libs/boost:= + sys-libs/libcap + dev-libs/libedit + dev-libs/libsodium:= + dnstap? ( dev-libs/fstrm ) + doh? ( net-libs/nghttp2:= ) + doh3? ( net-libs/quiche:= ) + lmdb? ( dev-db/lmdb:= ) + quic? ( net-libs/quiche ) + regex? ( dev-libs/re2:= ) + snmp? ( net-analyzer/net-snmp:= ) + ssl? ( dev-libs/openssl:= ) + systemd? ( sys-apps/systemd:0= ) + xdp? ( net-libs/xdp-tools ) + ${LUA_DEPS} +" + +DEPEND="${RDEPEND}" +BDEPEND="$(python_gen_any_dep 'dev-python/pyyaml[${PYTHON_USEDEP}]') + virtual/pkgconfig + yaml? ( ${RUST_DEPEND} ) +" + +# special requirements for live +if [[ ${PV} == *9999* ]] ; then + BDEPEND+=" dev-util/ragel" + S="${S}/pdns/dnsdistdist" +fi + +PATCHES=( + "${FILESDIR}"/2.0.2-roundrobin-fast-path.patch + "${FILESDIR}"/2.0.2-speed-up-cache-hits.patch +) + +pkg_setup() { + lua-single_pkg_setup + python-any-r1_pkg_setup + use yaml && rust_pkg_setup +} + +python_check_deps() { + python_has_version "dev-python/pyyaml[${PYTHON_USEDEP}]" +} + +# git-r3 overrides automatic SRC_URI unpacking +src_unpack() { + default + + if [[ ${PV} == *9999* ]] ; then + git-r3_src_unpack + fi +} + +src_prepare() { + default + + # clean up duplicate file + rm -f README.md +} + +src_configure() { + # bug #822855 + append-lfs-flags + + # There is currently no reliable way to handle mixed C++/Rust + LTO + # correctly: https://bugs.gentoo.org/963128 + if use yaml && tc-is-lto ; then + ewarn "Disabling LTO because of mixed C++/Rust toolchains." + filter-lto + fi + + # some things can only be enabled/disabled by defines + ! use dnstap && append-cppflags -DDISABLE_PROTOBUF + ! use web && append-cppflags -DDISABLE_BUILTIN_HTML + + local emesonargs=( + --sysconfdir="${EPREFIX}/etc/dnsdist" + # always use libsodium + -Dlibsodium=enabled + -Dlua=${ELUA} + # never try to build man pages (virtualenv) + -Dman-pages=false + # never use gnutls (openssl only) + -Dtls-gnutls=disabled + $(meson_feature bpf ebpf) + $(meson_feature cdb) + $(meson_feature dnscrypt) + $(meson_feature dnstap) + $(meson_feature doh dns-over-https) + $(meson_feature doh nghttp2) + $(meson_feature doh3 dns-over-http3) + $(meson_feature ipcipher) + $(meson_feature lmdb) + $(meson_feature quic dns-over-quic) + $(meson_feature regex re2) + $(meson_feature snmp) + $(meson_feature ssl libcrypto) + $(meson_feature ssl tls-libssl) + $(meson_feature ssl dns-over-tls) + $(meson_feature systemd systemd-service) + $(meson_use test unit-tests) + $(meson_feature xdp xsk) + $(meson_feature yaml) + ) + + meson_src_configure +} + +# explicitly implement src_compile/test to override the +# otherwise automagic cargo_src_compile/test phases + +src_compile() { + cargo_gen_config + cargo_env meson_src_compile +} + +src_test() { + meson_src_test +} + +src_install() { + meson_src_install + + use doc && dodoc -r "${WORKDIR}"/html + + insinto /etc/dnsdist + doins "${FILESDIR}"/dnsdist.conf.example + + newconfd "${FILESDIR}"/dnsdist.confd ${PN} + newinitd "${FILESDIR}"/dnsdist.initd ${PN} +} + +pkg_postinst() { + elog "dnsdist provides multiple instances support. You can create more instances" + elog "by symlinking the dnsdist init script to another name." + elog + elog "The name must be in the format dnsdist.<suffix> and dnsdist will use the" + elog "/etc/dnsdist/dnsdist-<suffix>.conf configuration file instead of the default." +} diff --git a/net-dns/dnsdist/files/2.0.2-roundrobin-fast-path.patch b/net-dns/dnsdist/files/2.0.2-roundrobin-fast-path.patch new file mode 100644 index 000000000000..c5829e1a15ee --- /dev/null +++ b/net-dns/dnsdist/files/2.0.2-roundrobin-fast-path.patch @@ -0,0 +1,39 @@ +Backport of: +https://github.com/PowerDNS/pdns/commit/495f8e5f1f2c147f7431c5a6bfd4f4606b640fe3 + +From: =?UTF-8?q?Holger=20Hoffst=C3=A4tte?= <[email protected]> +Date: Thu, 9 Oct 2025 22:04:07 +0200 +Subject: [PATCH] dnsdist: add fast path to roundrobin load balancing policy + +There is no need to collect all servers that are up when the current +server is already a good candidate. This avoids needless heap allocation +and deallocation in the vast majority of cases. + +Signed-off-by: Holger Hoffstätte <[email protected]> +--- a/dnsdist-lbpolicies.cc ++++ b/dnsdist-lbpolicies.cc +@@ -237,6 +237,14 @@ shared_ptr<DownstreamState> roundrobin(c + return shared_ptr<DownstreamState>(); + } + ++ static std::atomic<unsigned int> counter{0}; ++ ++ size_t serverIdx = (counter++) % servers.size(); ++ shared_ptr<DownstreamState> serverState = servers.at(serverIdx).second; ++ if (serverState->isUp()) { ++ return serverState; ++ } ++ + vector<size_t> candidates; + candidates.reserve(servers.size()); + +@@ -255,8 +264,7 @@ shared_ptr<DownstreamState> roundrobin(c + } + } + +- static std::atomic<unsigned int> counter{0}; +- return servers.at(candidates.at((counter++) % candidates.size()) - 1).second; ++ return servers.at(candidates.at(counter % candidates.size()) - 1).second; + } + + shared_ptr<DownstreamState> orderedWrandUntag(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dnsq) diff --git a/net-dns/dnsdist/files/2.0.2-speed-up-cache-hits.patch b/net-dns/dnsdist/files/2.0.2-speed-up-cache-hits.patch new file mode 100644 index 000000000000..ce145b031aab --- /dev/null +++ b/net-dns/dnsdist/files/2.0.2-speed-up-cache-hits.patch @@ -0,0 +1,327 @@ +Backport of: +https://github.com/PowerDNS/pdns/commit/8cad5a2288c9a4af5e6269277483df250adfce52 + +From: Remi Gacogne <[email protected]> +Date: Tue, 26 Aug 2025 14:00:26 +0200 +Subject: [PATCH] dnsdist: Speed up cache hits by skipping the LB policy when possible + +We use to execute the load-balancing policy to select a backend before +doing the cache lookup, because in some corner cases the selected +backend might have settings that impact our cache lookup. In practice +most configurations have a consistent set of settings for all servers +in a given pool, so it makes no sense to waste CPU cycles selecting a +backend if we are going to get a hit from the cache. +This PR adds a bit of code to check if a pool is in a consistent state, +and if it is it delays the execution of the load-balancing policy to +after the cache lookup, skipping it entirely for cache hits. + +Signed-off-by: Remi Gacogne <[email protected]> +--- a/dnsdist-backend.cc ++++ b/dnsdist-backend.cc +@@ -1049,6 +1049,18 @@ size_t ServerPool::poolLoad() + return load; + } + ++bool ServerPool::hasAtLeastOneServerAvailable() ++{ ++ auto servers = d_servers.read_lock(); ++ // NOLINTNEXTLINE(readability-use-anyofallof): no it's not more readable ++ for (const auto& server : **servers) { ++ if (std::get<1>(server)->isUp()) { ++ return true; ++ } ++ } ++ return false; ++} ++ + const std::shared_ptr<const ServerPolicy::NumberedServerVector> ServerPool::getServers() + { + std::shared_ptr<const ServerPolicy::NumberedServerVector> result; +@@ -1060,59 +1072,117 @@ const std::shared_ptr<const ServerPolicy::NumberedServerVector> ServerPool::getS + + void ServerPool::addServer(shared_ptr<DownstreamState>& server) + { +- auto servers = d_servers.write_lock(); +- /* we can't update the content of the shared pointer directly even when holding the lock, +- as other threads might hold a copy. We can however update the pointer as long as we hold the lock. */ +- unsigned int count = static_cast<unsigned int>((*servers)->size()); +- auto newServers = ServerPolicy::NumberedServerVector(*(*servers)); +- newServers.emplace_back(++count, server); +- /* we need to reorder based on the server 'order' */ +- std::stable_sort(newServers.begin(), newServers.end(), [](const std::pair<unsigned int,std::shared_ptr<DownstreamState> >& a, const std::pair<unsigned int,std::shared_ptr<DownstreamState> >& b) { +- return a.second->d_config.order < b.second->d_config.order; ++ { ++ auto servers = d_servers.write_lock(); ++ /* we can't update the content of the shared pointer directly even when holding the lock, ++ as other threads might hold a copy. We can however update the pointer as long as we hold the lock. */ ++ auto count = static_cast<unsigned int>((*servers)->size()); ++ auto newServers = ServerPolicy::NumberedServerVector(*(*servers)); ++ newServers.emplace_back(++count, server); ++ /* we need to reorder based on the server 'order' */ ++ std::stable_sort(newServers.begin(), newServers.end(), [](const std::pair<unsigned int,std::shared_ptr<DownstreamState> >& lhs, const std::pair<unsigned int,std::shared_ptr<DownstreamState> >& rhs) { ++ return lhs.second->d_config.order < rhs.second->d_config.order; + }); +- /* and now we need to renumber for Lua (custom policies) */ +- size_t idx = 1; +- for (auto& serv : newServers) { +- serv.first = idx++; +- } +- *servers = std::make_shared<const ServerPolicy::NumberedServerVector>(std::move(newServers)); ++ /* and now we need to renumber for Lua (custom policies) */ ++ size_t idx = 1; ++ for (auto& serv : newServers) { ++ 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; ++ if ((*servers)->size() == 1) { ++ d_tcpOnly = server->isTCPOnly(); ++ } ++ else if (!server->isTCPOnly()) { ++ d_tcpOnly = false; ++ } + } ++ ++ updateConsistency(); + } + + void ServerPool::removeServer(shared_ptr<DownstreamState>& server) + { +- auto servers = d_servers.write_lock(); +- /* we can't update the content of the shared pointer directly even when holding the lock, +- as other threads might hold a copy. We can however update the pointer as long as we hold the lock. */ +- 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();) { ++ { ++ auto servers = d_servers.write_lock(); ++ /* we can't update the content of the shared pointer directly even when holding the lock, ++ as other threads might hold a copy. We can however update the pointer as long as we hold the lock. */ ++ auto newServers = std::make_shared<ServerPolicy::NumberedServerVector>(*(*servers)); ++ ++ for (auto it = newServers->begin(); it != newServers->end();) { ++ if (found) { ++ /* we need to renumber the servers placed ++ after the removed one, for Lua (custom policies) */ ++ it->first = idx++; ++ it++; ++ } ++ else if (it->second == server) { ++ it = newServers->erase(it); ++ found = true; ++ } else { ++ idx++; ++ it++; ++ } ++ } ++ + 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++; +- it++; ++ *servers = std::move(newServers); ++ } ++ } ++ ++ if (found && !d_isConsistent) { ++ updateConsistency(); ++ } ++} ++ ++void ServerPool::updateConsistency() ++{ ++ bool first{true}; ++ bool useECS{false}; ++ bool tcpOnly{false}; ++ bool disableZeroScope{false}; ++ ++ auto servers = d_servers.read_lock(); ++ for (const auto& serverPair : **servers) { ++ const auto& server = serverPair.second; ++ if (first) { ++ first = false; ++ useECS = server->d_config.useECS; ++ tcpOnly = server->isTCPOnly(); ++ disableZeroScope = server->d_config.disableZeroScope; + } +- else if (it->second == server) { +- it = newServers->erase(it); +- found = true; +- } else { +- tcpOnly = tcpOnly && it->second->isTCPOnly(); +- idx++; +- it++; ++ else { ++ if (server->d_config.useECS != useECS || ++ server->isTCPOnly() != tcpOnly || ++ server->d_config.disableZeroScope != disableZeroScope) { ++ d_tcpOnly = false; ++ d_isConsistent = false; ++ return; ++ } + } + } ++ + d_tcpOnly = tcpOnly; +- *servers = std::move(newServers); ++ /* at this point we know that all servers agree ++ on these settings, so let's just use the same ++ values for the pool itself */ ++ d_useECS = useECS; ++ d_disableZeroScope = disableZeroScope; ++ d_isConsistent = true; ++} ++ ++void ServerPool::setDisableZeroScope(bool disable) ++{ ++ d_disableZeroScope = disable; ++ updateConsistency(); ++} ++ ++void ServerPool::setECS(bool useECS) ++{ ++ d_useECS = useECS; ++ updateConsistency(); + } + + namespace dnsdist::backend +--- a/dnsdist-lua-bindings.cc ++++ b/dnsdist-lua-bindings.cc +@@ -107,6 +107,8 @@ void setupLuaBindings(LuaContext& luaCtx, bool client, bool configCheck) + }); + luaCtx.registerFunction("getECS", &ServerPool::getECS); + luaCtx.registerFunction("setECS", &ServerPool::setECS); ++ luaCtx.registerFunction("getDisableZeroScope", &ServerPool::getDisableZeroScope); ++ luaCtx.registerFunction("setDisableZeroScope", &ServerPool::setDisableZeroScope); + + #ifndef DISABLE_DOWNSTREAM_BINDINGS + /* DownstreamState */ +--- a/dnsdist-settings-definitions.yml ++++ b/dnsdist-settings-definitions.yml +@@ -2040,6 +2040,14 @@ pool: + type: "String" + default: "" + description: "The name of the load-balancing policy associated to this pool. If left empty, the global policy will be used" ++ - name: "use_ecs" ++ type: "bool" ++ default: "false" ++ description: "Whether to add EDNS Client Subnet information to the query before looking up into the cache, when all servers from this pool are down. If at least one server is up, the preference of the selected server is used, this parameter is only useful if all the backends in this pool are down and have EDNS Client Subnet enabled, since the queries in the cache will have been inserted with ECS information" ++ - name: "disable_zero_scope" ++ type: "bool" ++ default: "false" ++ description: "Whether to disable the EDNS Client Subnet :doc:`../advanced/zero-scope` feature, which does a cache lookup for an answer valid for all subnets (ECS scope of 0) before adding ECS information to the query and doing the regular lookup, when all servers from this pool are down. If at least one server is up, the preference of the selected server is used, this parameter is only useful if all the backends in this pool are down, have EDNS Client Subnet enabled and zero scope disabled" + + custom_load_balancing_policy: + description: "Settings for a custom load-balancing policy" +--- a/dnsdist.cc ++++ b/dnsdist.cc +@@ -1441,7 +1441,13 @@ ProcessQueryResult processQueryAfterRules(DNSQuestion& dnsQuestion, std::shared_ + } + std::shared_ptr<ServerPool> serverPool = getPool(dnsQuestion.ids.poolName); + dnsQuestion.ids.packetCache = serverPool->packetCache; +- selectBackendForOutgoingQuery(dnsQuestion, serverPool, selectedBackend); ++ ++ bool backendLookupDone = false; ++ if (!dnsQuestion.ids.packetCache || !serverPool->isConsistent()) { ++ selectBackendForOutgoingQuery(dnsQuestion, serverPool, selectedBackend); ++ backendLookupDone = true; ++ } ++ + bool willBeForwardedOverUDP = !dnsQuestion.overTCP() || dnsQuestion.ids.protocol == dnsdist::Protocol::DoH; + if (selectedBackend && selectedBackend->isTCPOnly()) { + willBeForwardedOverUDP = false; +@@ -1450,17 +1456,22 @@ ProcessQueryResult processQueryAfterRules(DNSQuestion& dnsQuestion, std::shared_ + willBeForwardedOverUDP = !serverPool->isTCPOnly(); + } + +- uint32_t allowExpired = selectedBackend ? 0 : dnsdist::configuration::getCurrentRuntimeConfiguration().d_staleCacheEntriesTTL; ++ uint32_t allowExpired = 0; ++ if (!selectedBackend && dnsdist::configuration::getCurrentRuntimeConfiguration().d_staleCacheEntriesTTL > 0 && (backendLookupDone || !serverPool->hasAtLeastOneServerAvailable())) { ++ allowExpired = dnsdist::configuration::getCurrentRuntimeConfiguration().d_staleCacheEntriesTTL; ++ } + + if (dnsQuestion.ids.packetCache && !dnsQuestion.ids.skipCache && !dnsQuestion.ids.dnssecOK) { + dnsQuestion.ids.dnssecOK = (dnsdist::getEDNSZ(dnsQuestion) & EDNS_HEADER_FLAG_DO) != 0; + } + +- if (dnsQuestion.useECS && ((selectedBackend && selectedBackend->d_config.useECS) || (!selectedBackend && serverPool->getECS()))) { ++ const bool useECS = dnsQuestion.useECS && ((selectedBackend && selectedBackend->d_config.useECS) || (!selectedBackend && serverPool->getECS())); ++ if (useECS) { ++ const bool useZeroScope = (selectedBackend && !selectedBackend->d_config.disableZeroScope) || (!selectedBackend && !serverPool->getDisableZeroScope()); + // we special case our cache in case a downstream explicitly gave us a universally valid response with a 0 scope + // 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 (dnsQuestion.ids.packetCache && !dnsQuestion.ids.skipCache && (!selectedBackend || !selectedBackend->d_config.disableZeroScope) && dnsQuestion.ids.packetCache->isECSParsingEnabled()) { ++ if (dnsQuestion.ids.packetCache && !dnsQuestion.ids.skipCache && useZeroScope && dnsQuestion.ids.packetCache->isECSParsingEnabled()) { + if (dnsQuestion.ids.packetCache->get(dnsQuestion, dnsQuestion.getHeader()->id, &dnsQuestion.ids.cacheKeyNoECS, dnsQuestion.ids.subnet, *dnsQuestion.ids.dnssecOK, willBeForwardedOverUDP, allowExpired, false, true, false)) { + + vinfolog("Packet cache hit for query for %s|%s from %s (%s, %d bytes)", dnsQuestion.ids.qname.toLogString(), QType(dnsQuestion.ids.qtype).toString(), dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.protocol.toString(), dnsQuestion.getData().size()); +@@ -1543,9 +1554,14 @@ ProcessQueryResult processQueryAfterRules(DNSQuestion& dnsQuestion, std::shared_ + serverPool = getPool(dnsQuestion.ids.poolName); + dnsQuestion.ids.packetCache = serverPool->packetCache; + selectBackendForOutgoingQuery(dnsQuestion, serverPool, selectedBackend); ++ backendLookupDone = true; + } + } + ++ if (!backendLookupDone) { ++ selectBackendForOutgoingQuery(dnsQuestion, serverPool, selectedBackend); ++ } ++ + if (!selectedBackend) { + auto servFailOnNoPolicy = dnsdist::configuration::getCurrentRuntimeConfiguration().d_servFailOnNoPolicy; + ++dnsdist::metrics::g_stats.noPolicy; +--- a/dnsdist.hh ++++ b/dnsdist.hh +@@ -951,9 +951,18 @@ struct ServerPool + return d_useECS; + } + +- void setECS(bool useECS) ++ void setECS(bool useECS); ++ ++ bool getDisableZeroScope() const ++ { ++ return d_disableZeroScope; ++ } ++ ++ void setDisableZeroScope(bool disable); ++ ++ bool isConsistent() const + { +- d_useECS = useECS; ++ return d_isConsistent; + } + + std::shared_ptr<DNSDistPacketCache> packetCache{nullptr}; +@@ -961,6 +970,7 @@ struct ServerPool + + size_t poolLoad(); + size_t countServers(bool upOnly); ++ bool hasAtLeastOneServerAvailable(); + const std::shared_ptr<const ServerPolicy::NumberedServerVector> getServers(); + void addServer(shared_ptr<DownstreamState>& server); + void removeServer(shared_ptr<DownstreamState>& server); +@@ -971,9 +981,13 @@ struct ServerPool + } + + private: ++ void updateConsistency(); ++ + SharedLockGuarded<std::shared_ptr<const ServerPolicy::NumberedServerVector>> d_servers; + bool d_useECS{false}; + bool d_tcpOnly{false}; ++ bool d_disableZeroScope{false}; ++ bool d_isConsistent{true}; + }; + + enum ednsHeaderFlags
