Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package dnsdist for openSUSE:Factory checked 
in at 2025-05-26 18:40:36
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/dnsdist (Old)
 and      /work/SRC/openSUSE:Factory/.dnsdist.new.2732 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "dnsdist"

Mon May 26 18:40:36 2025 rev:13 rq:1280131 version:1.9.10

Changes:
--------
--- /work/SRC/openSUSE:Factory/dnsdist/dnsdist.changes  2024-11-05 
15:42:02.844656439 +0100
+++ /work/SRC/openSUSE:Factory/.dnsdist.new.2732/dnsdist.changes        
2025-05-26 18:41:48.859241858 +0200
@@ -1,0 +2,6 @@
+Mon May 26 11:13:10 UTC 2025 - Adam Majer <adam.ma...@suse.de>
+
+- update to 1.9.10: (bsc#1243378, bsc#1242028, CVE-2025-30194, CVE-2025-30193)
+  https://www.dnsdist.org/changelog.html#change-1.9.10
+
+-------------------------------------------------------------------

Old:
----
  dnsdist-1.9.7.tar.bz2
  dnsdist-1.9.7.tar.bz2.sig

New:
----
  dnsdist-1.9.10.tar.bz2
  dnsdist-1.9.10.tar.bz2.sig

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ dnsdist.spec ++++++
--- /var/tmp/diff_new_pack.ZivKKn/_old  2025-05-26 18:41:49.299260342 +0200
+++ /var/tmp/diff_new_pack.ZivKKn/_new  2025-05-26 18:41:49.299260342 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package dnsdist
 #
-# Copyright (c) 2024 SUSE LLC
+# Copyright (c) 2025 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -38,7 +38,7 @@
 %bcond_with     dnsdist_luajit
 %endif
 Name:           dnsdist
-Version:        1.9.7
+Version:        1.9.10
 Release:        0
 Summary:        A highly DNS-, DoS- and abuse-aware loadbalancer
 License:        GPL-2.0-only

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

Reply via email to