Bug#1106210: unblock: dnsdist/1.9.10-1 [pre-approval, security]
Control: tags -1 confirmed
On 2025-05-21 11:03:59 +0200, Chris Hofstädtler wrote:
> Package: release.debian.org
> Severity: normal
> User: release.debian.org@packages.debian.org
> Usertags: unblock
> X-Debbugs-Cc: dnsdist@packages.debian.org, team@security.debian.org
> Control: affects -1 + src:dnsdist
>
> Please unblock package dnsdist
>
> [ Reason ]
> New upstream bugfix release with fix for security issue CVE-2025-30193 #1106207
ACK, please go ahead.
Cheers
>
> I've picked the complete upstream minor release instead of
> cherry-picking the single fix, as the remaining diff is small. In
> addition to the CVE fix, we get: 1) fix for newer systemd versions /
> socket-family sandboxing, 2) fix for newer prometheus scrapers,
> 3) tiny feature to get the incoming network interface in Lua
> scripting.
>
> [ Impact ]
> CVE-2025-30193 will be unfixed if not uploaded.
>
> [ Tests ]
> I've reviewed the diff, did a test build and did a runtime test on a
> very small setup.
>
> [ Risks ]
> IMO the security fix is the large part of the diff, the rest seems
> trivial to me.
>
> [ Checklist ]
> [x] all changes are documented in the d/changelog
> [x] I reviewed all changes and I approve them
> [x] attach debdiff against the package in testing
>
> [ Other info ]
> Nothing I'm aware of.
>
> unblock dnsdist/1.9.10-1
> diff -Nru dnsdist-1.9.9/configure dnsdist-1.9.10/configure
> --- dnsdist-1.9.9/configure 2025-04-29 11:46:28.000000000 +0200
> +++ dnsdist-1.9.10/configure 2025-05-20 11:13:44.000000000 +0200
> @@ -1,6 +1,6 @@
> #! /bin/sh
> # Guess values for system-dependent variables and create Makefiles.
> -# Generated by GNU Autoconf 2.71 for dnsdist 1.9.9.
> +# Generated by GNU Autoconf 2.71 for dnsdist 1.9.10.
> #
> #
> # Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation,
> @@ -618,8 +618,8 @@
> # Identity of this package.
> PACKAGE_NAME='dnsdist'
> PACKAGE_TARNAME='dnsdist'
> -PACKAGE_VERSION='1.9.9'
> -PACKAGE_STRING='dnsdist 1.9.9'
> +PACKAGE_VERSION='1.9.10'
> +PACKAGE_STRING='dnsdist 1.9.10'
> PACKAGE_BUGREPORT=''
> PACKAGE_URL=''
>
> @@ -1645,7 +1645,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 1.9.9 to adapt to many kinds of systems.
> +\`configure' configures dnsdist 1.9.10 to adapt to many kinds of systems.
>
> Usage: $0 [OPTION]... [VAR=VALUE]...
>
> @@ -1716,7 +1716,7 @@
>
> if test -n "$ac_init_help"; then
> case $ac_init_help in
> - short | recursive ) echo "Configuration of dnsdist 1.9.9:";;
> + short | recursive ) echo "Configuration of dnsdist 1.9.10:";;
> esac
> cat <<\_ACEOF
>
> @@ -1951,7 +1951,7 @@
> test -n "$ac_init_help" && exit $ac_status
> if $ac_init_version; then
> cat <<\_ACEOF
> -dnsdist configure 1.9.9
> +dnsdist configure 1.9.10
> generated by GNU Autoconf 2.71
>
> Copyright (C) 2021 Free Software Foundation, Inc.
> @@ -2440,7 +2440,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 1.9.9, which was
> +It was created by dnsdist $as_me 1.9.10, which was
> generated by GNU Autoconf 2.71. Invocation command line was
>
> $ $0$ac_configure_args_raw
> @@ -3932,7 +3932,7 @@
>
> # Define the identity of the package.
> PACKAGE='dnsdist'
> - VERSION='1.9.9'
> + VERSION='1.9.10'
>
>
> printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
> @@ -28627,7 +28627,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 1.9.9, which was
> +This file was extended by dnsdist $as_me 1.9.10, which was
> generated by GNU Autoconf 2.71. Invocation command line was
>
> CONFIG_FILES = $CONFIG_FILES
> @@ -28695,7 +28695,7 @@
> cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
> ac_cs_config='$ac_cs_config_escaped'
> ac_cs_version="\\
> -dnsdist config.status 1.9.9
> +dnsdist config.status 1.9.10
> configured by $0, generated by GNU Autoconf 2.71,
> with options \\"\$ac_cs_config\\"
>
> diff -Nru dnsdist-1.9.9/configure.ac dnsdist-1.9.10/configure.ac
> --- dnsdist-1.9.9/configure.ac 2025-04-29 11:46:19.000000000 +0200
> +++ 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.9])
> +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 -Nru dnsdist-1.9.9/credentials.hh dnsdist-1.9.10/credentials.hh
> --- dnsdist-1.9.9/credentials.hh 2025-04-29 11:46:03.000000000 +0200
> +++ 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 -Nru dnsdist-1.9.9/debian/changelog dnsdist-1.9.10/debian/changelog
> --- dnsdist-1.9.9/debian/changelog 2025-04-29 14:27:45.000000000 +0200
> +++ dnsdist-1.9.10/debian/changelog 2025-05-21 10:30:17.000000000 +0200
> @@ -1,3 +1,10 @@
> +dnsdist (1.9.10-1) unstable; urgency=medium
> +
> + * New upstream version 1.9.10 including fix for CVE-2025-30193
> + (Closes: #1106207)
> +
> + -- Chris Hofstaedtler <zeha@debian.org> Wed, 21 May 2025 10:30:17 +0200
> +
> dnsdist (1.9.9-1) unstable; urgency=medium
>
> * New upstream version 1.9.9 including fix for CVE-2025-30194
> diff -Nru dnsdist-1.9.9/dnsdist.1 dnsdist-1.9.10/dnsdist.1
> --- dnsdist-1.9.9/dnsdist.1 2025-04-29 11:47:05.000000000 +0200
> +++ 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" "Apr 29, 2025" "" "dnsdist"
> +.TH "DNSDIST" "1" "May 20, 2025" "" "dnsdist"
> .SH NAME
> dnsdist \- A DNS and DoS aware, scriptable loadbalancer
> .SH SYNOPSIS
> diff -Nru dnsdist-1.9.9/dnsdist-backend.cc dnsdist-1.9.10/dnsdist-backend.cc
> --- dnsdist-1.9.9/dnsdist-backend.cc 2025-04-29 11:46:04.000000000 +0200
> +++ 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 -Nru dnsdist-1.9.9/dnsdist.cc dnsdist-1.9.10/dnsdist.cc
> --- dnsdist-1.9.9/dnsdist.cc 2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/dnsdist.cc 2025-05-20 11:13:25.000000000 +0200
> @@ -1447,6 +1447,9 @@
> if (selectedBackend && selectedBackend->isTCPOnly()) {
> willBeForwardedOverUDP = false;
> }
> + else if (!selectedBackend) {
> + willBeForwardedOverUDP = !serverPool->isTCPOnly();
> + }
>
> uint32_t allowExpired = selectedBackend ? 0 : g_staleCacheEntriesTTL;
>
> @@ -1693,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 -Nru dnsdist-1.9.9/dnsdist.hh dnsdist-1.9.10/dnsdist.hh
> --- dnsdist-1.9.9/dnsdist.hh 2025-04-29 11:46:04.000000000 +0200
> +++ 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 -Nru dnsdist-1.9.9/dnsdist-lua-bindings.cc dnsdist-1.9.10/dnsdist-lua-bindings.cc
> --- dnsdist-1.9.9/dnsdist-lua-bindings.cc 2025-04-29 11:46:04.000000000 +0200
> +++ 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 -Nru dnsdist-1.9.9/dnsdist-lua-bindings-dnsquestion.cc dnsdist-1.9.10/dnsdist-lua-bindings-dnsquestion.cc
> --- dnsdist-1.9.9/dnsdist-lua-bindings-dnsquestion.cc 2025-04-29 11:46:04.000000000 +0200
> +++ 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();
> });
> @@ -458,6 +465,13 @@
> 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) {
> @@ -551,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 -Nru dnsdist-1.9.9/dnsdist-lua.cc dnsdist-1.9.10/dnsdist-lua.cc
> --- dnsdist-1.9.9/dnsdist-lua.cc 2025-04-29 11:46:04.000000000 +0200
> +++ 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());
> diff -Nru dnsdist-1.9.9/dnsdist-lua-ffi.cc dnsdist-1.9.10/dnsdist-lua-ffi.cc
> --- dnsdist-1.9.9/dnsdist-lua-ffi.cc 2025-04-29 11:46:04.000000000 +0200
> +++ 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 -Nru dnsdist-1.9.9/dnsdist-lua-ffi-interface.h dnsdist-1.9.10/dnsdist-lua-ffi-interface.h
> --- dnsdist-1.9.9/dnsdist-lua-ffi-interface.h 2025-04-29 11:46:04.000000000 +0200
> +++ 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 -Nru dnsdist-1.9.9/dnsdist-lua-ffi-interface.inc dnsdist-1.9.10/dnsdist-lua-ffi-interface.inc
> --- dnsdist-1.9.9/dnsdist-lua-ffi-interface.inc 2025-04-29 11:46:38.000000000 +0200
> +++ 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 -Nru dnsdist-1.9.9/dnsdist-proxy-protocol.cc dnsdist-1.9.10/dnsdist-proxy-protocol.cc
> --- dnsdist-1.9.9/dnsdist-proxy-protocol.cc 2025-04-29 11:46:04.000000000 +0200
> +++ 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 -Nru dnsdist-1.9.9/dnsdist-proxy-protocol.hh dnsdist-1.9.10/dnsdist-proxy-protocol.hh
> --- dnsdist-1.9.9/dnsdist-proxy-protocol.hh 2025-04-29 11:46:04.000000000 +0200
> +++ 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 -Nru dnsdist-1.9.9/dnsdist.service.in dnsdist-1.9.10/dnsdist.service.in
> --- dnsdist-1.9.9/dnsdist.service.in 2025-04-29 11:46:04.000000000 +0200
> +++ 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 -Nru dnsdist-1.9.9/dnsdist-tcp.cc dnsdist-1.9.10/dnsdist-tcp.cc
> --- dnsdist-1.9.9/dnsdist-tcp.cc 2025-04-29 11:46:04.000000000 +0200
> +++ 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 -Nru dnsdist-1.9.9/dnsdist-tcp-upstream.hh dnsdist-1.9.10/dnsdist-tcp-upstream.hh
> --- dnsdist-1.9.9/dnsdist-tcp-upstream.hh 2025-04-29 11:46:04.000000000 +0200
> +++ 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 -Nru dnsdist-1.9.9/dnsdist-web.cc dnsdist-1.9.10/dnsdist-web.cc
> --- dnsdist-1.9.9/dnsdist-web.cc 2025-04-29 11:46:04.000000000 +0200
> +++ 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 -Nru dnsdist-1.9.9/doh3.cc dnsdist-1.9.10/doh3.cc
> --- dnsdist-1.9.9/doh3.cc 2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/doh3.cc 2025-05-20 11:13:25.000000000 +0200
> @@ -912,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;
> }
>
> @@ -966,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");
> @@ -1011,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 -Nru dnsdist-1.9.9/doq.cc dnsdist-1.9.10/doq.cc
> --- dnsdist-1.9.9/doq.cc 2025-04-29 11:46:04.000000000 +0200
> +++ 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 -Nru dnsdist-1.9.9/doq-common.cc dnsdist-1.9.10/doq-common.cc
> --- dnsdist-1.9.9/doq-common.cc 2025-04-29 11:46:04.000000000 +0200
> +++ 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 -Nru dnsdist-1.9.9/doq-common.hh dnsdist-1.9.10/doq-common.hh
> --- dnsdist-1.9.9/doq-common.hh 2025-04-29 11:46:04.000000000 +0200
> +++ 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 -Nru dnsdist-1.9.9/iputils.hh dnsdist-1.9.10/iputils.hh
> --- dnsdist-1.9.9/iputils.hh 2025-04-29 11:46:04.000000000 +0200
> +++ 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 -Nru dnsdist-1.9.9/noinitvector.hh dnsdist-1.9.10/noinitvector.hh
> --- dnsdist-1.9.9/noinitvector.hh 2025-04-29 11:46:04.000000000 +0200
> +++ 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 -Nru dnsdist-1.9.9/test-dnsdist-lua-ffi.cc dnsdist-1.9.10/test-dnsdist-lua-ffi.cc
> --- dnsdist-1.9.9/test-dnsdist-lua-ffi.cc 2025-04-29 11:46:04.000000000 +0200
> +++ 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 -Nru dnsdist-1.9.9/test-dnsdisttcp_cc.cc dnsdist-1.9.10/test-dnsdisttcp_cc.cc
> --- dnsdist-1.9.9/test-dnsdisttcp_cc.cc 2025-04-29 11:46:04.000000000 +0200
> +++ 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();
--
Sebastian Ramacher
Reply to: