Your message dated Mon, 26 May 2025 13:38:13 +0000
with message-id <e1ujy1t-002aed...@respighi.debian.org>
and subject line unblock dnsdist
has caused the Debian Bug report #1106210,
regarding unblock: dnsdist/1.9.10-1 [pre-approval, security]
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact ow...@bugs.debian.org
immediately.)


-- 
1106210: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1106210
Debian Bug Tracking System
Contact ow...@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
User: release.debian....@packages.debian.org
Usertags: unblock
X-Debbugs-Cc: dnsd...@packages.debian.org, t...@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

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 <z...@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();

--- End Message ---
--- Begin Message ---
Unblocked.

--- End Message ---

Reply via email to