[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1106210: unblock: dnsdist/1.9.10-1 [pre-approval, security]



Control: tags -1 confirmed

On 2025-05-21 11:03:59 +0200, Chris Hofstädtler wrote:
> Package: release.debian.org
> Severity: normal
> User: release.debian.org@packages.debian.org
> Usertags: unblock
> X-Debbugs-Cc: dnsdist@packages.debian.org, team@security.debian.org
> Control: affects -1 + src:dnsdist
> 
> Please unblock package dnsdist
> 
> [ Reason ]
> New upstream bugfix release with fix for security issue CVE-2025-30193 #1106207

ACK, please go ahead.

Cheers

> 
> I've picked the complete upstream minor release instead of 
> cherry-picking the single fix, as the remaining diff is small. In 
> addition to the CVE fix, we get: 1) fix for newer systemd versions / 
> socket-family sandboxing, 2) fix for newer prometheus scrapers,
> 3) tiny feature to get the incoming network interface in Lua 
> scripting.
> 
> [ Impact ]
> CVE-2025-30193 will be unfixed if not uploaded.
> 
> [ Tests ]
> I've reviewed the diff, did a test build and did a runtime test on a 
> very small setup.
> 
> [ Risks ]
> IMO the security fix is the large part of the diff, the rest seems 
> trivial to me.
> 
> [ Checklist ]
>   [x] all changes are documented in the d/changelog
>   [x] I reviewed all changes and I approve them
>   [x] attach debdiff against the package in testing
> 
> [ Other info ]
> Nothing I'm aware of.
> 
> unblock dnsdist/1.9.10-1

> diff -Nru dnsdist-1.9.9/configure dnsdist-1.9.10/configure
> --- dnsdist-1.9.9/configure	2025-04-29 11:46:28.000000000 +0200
> +++ dnsdist-1.9.10/configure	2025-05-20 11:13:44.000000000 +0200
> @@ -1,6 +1,6 @@
>  #! /bin/sh
>  # Guess values for system-dependent variables and create Makefiles.
> -# Generated by GNU Autoconf 2.71 for dnsdist 1.9.9.
> +# Generated by GNU Autoconf 2.71 for dnsdist 1.9.10.
>  #
>  #
>  # Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation,
> @@ -618,8 +618,8 @@
>  # Identity of this package.
>  PACKAGE_NAME='dnsdist'
>  PACKAGE_TARNAME='dnsdist'
> -PACKAGE_VERSION='1.9.9'
> -PACKAGE_STRING='dnsdist 1.9.9'
> +PACKAGE_VERSION='1.9.10'
> +PACKAGE_STRING='dnsdist 1.9.10'
>  PACKAGE_BUGREPORT=''
>  PACKAGE_URL=''
>  
> @@ -1645,7 +1645,7 @@
>    # Omit some internal or obsolete options to make the list less imposing.
>    # This message is too long to be a string in the A/UX 3.1 sh.
>    cat <<_ACEOF
> -\`configure' configures dnsdist 1.9.9 to adapt to many kinds of systems.
> +\`configure' configures dnsdist 1.9.10 to adapt to many kinds of systems.
>  
>  Usage: $0 [OPTION]... [VAR=VALUE]...
>  
> @@ -1716,7 +1716,7 @@
>  
>  if test -n "$ac_init_help"; then
>    case $ac_init_help in
> -     short | recursive ) echo "Configuration of dnsdist 1.9.9:";;
> +     short | recursive ) echo "Configuration of dnsdist 1.9.10:";;
>     esac
>    cat <<\_ACEOF
>  
> @@ -1951,7 +1951,7 @@
>  test -n "$ac_init_help" && exit $ac_status
>  if $ac_init_version; then
>    cat <<\_ACEOF
> -dnsdist configure 1.9.9
> +dnsdist configure 1.9.10
>  generated by GNU Autoconf 2.71
>  
>  Copyright (C) 2021 Free Software Foundation, Inc.
> @@ -2440,7 +2440,7 @@
>  This file contains any messages produced by compilers while
>  running configure, to aid debugging if configure makes a mistake.
>  
> -It was created by dnsdist $as_me 1.9.9, which was
> +It was created by dnsdist $as_me 1.9.10, which was
>  generated by GNU Autoconf 2.71.  Invocation command line was
>  
>    $ $0$ac_configure_args_raw
> @@ -3932,7 +3932,7 @@
>  
>  # Define the identity of the package.
>   PACKAGE='dnsdist'
> - VERSION='1.9.9'
> + VERSION='1.9.10'
>  
>  
>  printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
> @@ -28627,7 +28627,7 @@
>  # report actual input values of CONFIG_FILES etc. instead of their
>  # values after options handling.
>  ac_log="
> -This file was extended by dnsdist $as_me 1.9.9, which was
> +This file was extended by dnsdist $as_me 1.9.10, which was
>  generated by GNU Autoconf 2.71.  Invocation command line was
>  
>    CONFIG_FILES    = $CONFIG_FILES
> @@ -28695,7 +28695,7 @@
>  cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
>  ac_cs_config='$ac_cs_config_escaped'
>  ac_cs_version="\\
> -dnsdist config.status 1.9.9
> +dnsdist config.status 1.9.10
>  configured by $0, generated by GNU Autoconf 2.71,
>    with options \\"\$ac_cs_config\\"
>  
> diff -Nru dnsdist-1.9.9/configure.ac dnsdist-1.9.10/configure.ac
> --- dnsdist-1.9.9/configure.ac	2025-04-29 11:46:19.000000000 +0200
> +++ dnsdist-1.9.10/configure.ac	2025-05-20 11:13:35.000000000 +0200
> @@ -1,6 +1,6 @@
>  AC_PREREQ([2.69])
>  
> -AC_INIT([dnsdist], [1.9.9])
> +AC_INIT([dnsdist], [1.9.10])
>  AM_INIT_AUTOMAKE([foreign tar-ustar dist-bzip2 no-dist-gzip parallel-tests 1.11 subdir-objects])
>  AM_SILENT_RULES([yes])
>  AC_CONFIG_MACRO_DIR([m4])
> diff -Nru dnsdist-1.9.9/credentials.hh dnsdist-1.9.10/credentials.hh
> --- dnsdist-1.9.9/credentials.hh	2025-04-29 11:46:03.000000000 +0200
> +++ dnsdist-1.9.10/credentials.hh	2025-05-20 11:13:25.000000000 +0200
> @@ -21,7 +21,7 @@
>   */
>  #pragma once
>  
> -#include <memory>
> +#include <cstdint>
>  #include <string>
>  
>  class SensitiveData
> diff -Nru dnsdist-1.9.9/debian/changelog dnsdist-1.9.10/debian/changelog
> --- dnsdist-1.9.9/debian/changelog	2025-04-29 14:27:45.000000000 +0200
> +++ dnsdist-1.9.10/debian/changelog	2025-05-21 10:30:17.000000000 +0200
> @@ -1,3 +1,10 @@
> +dnsdist (1.9.10-1) unstable; urgency=medium
> +
> +  * New upstream version 1.9.10 including fix for CVE-2025-30193
> +    (Closes: #1106207)
> +
> + -- Chris Hofstaedtler <zeha@debian.org>  Wed, 21 May 2025 10:30:17 +0200
> +
>  dnsdist (1.9.9-1) unstable; urgency=medium
>  
>    * New upstream version 1.9.9 including fix for CVE-2025-30194
> diff -Nru dnsdist-1.9.9/dnsdist.1 dnsdist-1.9.10/dnsdist.1
> --- dnsdist-1.9.9/dnsdist.1	2025-04-29 11:47:05.000000000 +0200
> +++ dnsdist-1.9.10/dnsdist.1	2025-05-20 11:14:13.000000000 +0200
> @@ -27,7 +27,7 @@
>  .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
>  .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
>  ..
> -.TH "DNSDIST" "1" "Apr 29, 2025" "" "dnsdist"
> +.TH "DNSDIST" "1" "May 20, 2025" "" "dnsdist"
>  .SH NAME
>  dnsdist \- A DNS and DoS aware, scriptable loadbalancer
>  .SH SYNOPSIS
> diff -Nru dnsdist-1.9.9/dnsdist-backend.cc dnsdist-1.9.10/dnsdist-backend.cc
> --- dnsdist-1.9.9/dnsdist-backend.cc	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/dnsdist-backend.cc	2025-05-20 11:13:25.000000000 +0200
> @@ -144,7 +144,12 @@
>      }
>      catch (const std::runtime_error& error) {
>        if (initialAttempt || g_verbose) {
> -        infolog("Error connecting to new server with address %s: %s", d_config.remote.toStringWithPort(), error.what());
> +        if (!IsAnyAddress(d_config.sourceAddr) || !d_config.sourceItfName.empty()) {
> +          infolog("Error connecting to new server with address %s (source address: %s, source interface: %s): %s", d_config.remote.toStringWithPort(), IsAnyAddress(d_config.sourceAddr) ? "not set" : d_config.sourceAddr.toString(), d_config.sourceItfName.empty() ? "not set" : d_config.sourceItfName, error.what());
> +        }
> +        else {
> +          infolog("Error connecting to new server with address %s: %s", d_config.remote.toStringWithPort(), error.what());
> +        }
>        }
>        connected = false;
>        break;
> @@ -974,6 +979,13 @@
>      serv.first = idx++;
>    }
>    *servers = std::make_shared<const ServerPolicy::NumberedServerVector>(std::move(newServers));
> +
> +  if ((*servers)->size() == 1) {
> +    d_tcpOnly = server->isTCPOnly();
> +  }
> +  else if (!server->isTCPOnly()) {
> +    d_tcpOnly = false;
> +  }
>  }
>  
>  void ServerPool::removeServer(shared_ptr<DownstreamState>& server)
> @@ -984,8 +996,10 @@
>    auto newServers = std::make_shared<ServerPolicy::NumberedServerVector>(*(*servers));
>    size_t idx = 1;
>    bool found = false;
> +  bool tcpOnly = true;
>    for (auto it = newServers->begin(); it != newServers->end();) {
>      if (found) {
> +      tcpOnly = tcpOnly && it->second->isTCPOnly();
>        /* we need to renumber the servers placed
>           after the removed one, for Lua (custom policies) */
>        it->first = idx++;
> @@ -995,9 +1009,11 @@
>        it = newServers->erase(it);
>        found = true;
>      } else {
> +      tcpOnly = tcpOnly && it->second->isTCPOnly();
>        idx++;
>        it++;
>      }
>    }
> +  d_tcpOnly = tcpOnly;
>    *servers = std::move(newServers);
>  }
> diff -Nru dnsdist-1.9.9/dnsdist.cc dnsdist-1.9.10/dnsdist.cc
> --- dnsdist-1.9.9/dnsdist.cc	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/dnsdist.cc	2025-05-20 11:13:25.000000000 +0200
> @@ -1447,6 +1447,9 @@
>      if (selectedBackend && selectedBackend->isTCPOnly()) {
>        willBeForwardedOverUDP = false;
>      }
> +    else if (!selectedBackend) {
> +      willBeForwardedOverUDP = !serverPool->isTCPOnly();
> +    }
>  
>      uint32_t allowExpired = selectedBackend ? 0 : g_staleCacheEntriesTTL;
>  
> @@ -1693,9 +1696,13 @@
>    bool doh = dnsQuestion.ids.du != nullptr;
>  
>    bool failed = false;
> +  dnsQuestion.ids.d_proxyProtocolPayloadSize = 0;
>    if (downstream->d_config.useProxyProtocol) {
>      try {
> -      addProxyProtocol(dnsQuestion, &dnsQuestion.ids.d_proxyProtocolPayloadSize);
> +      size_t proxyProtocolPayloadSize = 0;
> +      if (addProxyProtocol(dnsQuestion, &proxyProtocolPayloadSize)) {
> +        dnsQuestion.ids.d_proxyProtocolPayloadSize += proxyProtocolPayloadSize;
> +      }
>      }
>      catch (const std::exception& e) {
>        vinfolog("Adding proxy protocol payload to %s query from %s failed: %s", (dnsQuestion.ids.du ? "DoH" : ""), dnsQuestion.ids.origDest.toStringWithPort(), e.what());
> diff -Nru dnsdist-1.9.9/dnsdist.hh dnsdist-1.9.10/dnsdist.hh
> --- dnsdist-1.9.9/dnsdist.hh	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/dnsdist.hh	2025-05-20 11:13:25.000000000 +0200
> @@ -971,7 +971,7 @@
>      return d_config.d_tcpOnly || d_config.d_tcpCheck || d_tlsCtx != nullptr;
>    }
>  
> -  bool isTCPOnly() const
> +  [[nodiscard]] bool isTCPOnly() const
>    {
>      return d_config.d_tcpOnly || d_tlsCtx != nullptr;
>    }
> @@ -1071,10 +1071,15 @@
>    const std::shared_ptr<const ServerPolicy::NumberedServerVector> getServers();
>    void addServer(shared_ptr<DownstreamState>& server);
>    void removeServer(shared_ptr<DownstreamState>& server);
> +   bool isTCPOnly() const
> +  {
> +    return d_tcpOnly;
> +  }
>  
>  private:
>    SharedLockGuarded<std::shared_ptr<const ServerPolicy::NumberedServerVector>> d_servers;
>    bool d_useECS{false};
> +  bool d_tcpOnly{false};
>  };
>  
>  enum ednsHeaderFlags {
> diff -Nru dnsdist-1.9.9/dnsdist-lua-bindings.cc dnsdist-1.9.10/dnsdist-lua-bindings.cc
> --- dnsdist-1.9.9/dnsdist-lua-bindings.cc	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/dnsdist-lua-bindings.cc	2025-05-20 11:13:25.000000000 +0200
> @@ -845,7 +845,7 @@
>      if (client || configCheck) {
>        return;
>      }
> -    std::thread newThread(dnsdist::resolver::asynchronousResolver, std::move(hostname), [callback=std::move(callback)](const std::string& resolvedHostname, std::vector<ComboAddress>& ips) {
> +    std::thread newThread(dnsdist::resolver::asynchronousResolver, std::move(hostname), [callback = std::move(callback)](const std::string& resolvedHostname, std::vector<ComboAddress>& ips) mutable {
>        LuaArray<ComboAddress> result;
>        result.reserve(ips.size());
>        for (const auto& entry : ips) {
> @@ -853,7 +853,15 @@
>        }
>        {
>          auto lua = g_lua.lock();
> -        callback(resolvedHostname, result);
> +        try {
> +          callback(resolvedHostname, result);
> +        }
> +        catch (const std::exception& exp) {
> +          vinfolog("Error during execution of getAddressInfo callback: %s", exp.what());
> +        }
> +        // this _needs_ to be done while we are holding the lock,
> +        // otherwise the destructor will corrupt the stack
> +        callback = nullptr;
>          dnsdist::handleQueuedAsynchronousEvents();
>        }
>      });
> diff -Nru dnsdist-1.9.9/dnsdist-lua-bindings-dnsquestion.cc dnsdist-1.9.10/dnsdist-lua-bindings-dnsquestion.cc
> --- dnsdist-1.9.9/dnsdist-lua-bindings-dnsquestion.cc	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/dnsdist-lua-bindings-dnsquestion.cc	2025-05-20 11:13:25.000000000 +0200
> @@ -138,6 +138,13 @@
>        return dq.sni;
>      });
>  
> +  luaCtx.registerFunction<std::string (DNSQuestion::*)() const>("getIncomingInterface", [](const DNSQuestion& dnsQuestion) -> std::string {
> +    if (dnsQuestion.ids.cs != nullptr) {
> +      return dnsQuestion.ids.cs->interface;
> +    }
> +    return {};
> +  });
> +
>    luaCtx.registerFunction<std::string (DNSQuestion::*)()const>("getProtocol", [](const DNSQuestion& dq) {
>      return dq.getProtocol().toPrettyString();
>    });
> @@ -458,6 +465,13 @@
>      return dnsResponse.ids.queryRealTime.udiff();
>    });
>  
> +  luaCtx.registerFunction<std::string (DNSResponse::*)() const>("getIncomingInterface", [](const DNSResponse& dnsResponse) -> std::string {
> +    if (dnsResponse.ids.cs != nullptr) {
> +      return dnsResponse.ids.cs->interface;
> +    }
> +    return {};
> +  });
> +
>    luaCtx.registerFunction<void(DNSResponse::*)(std::string)>("sendTrap", [](const DNSResponse& dr, boost::optional<std::string> reason) {
>  #ifdef HAVE_NET_SNMP
>        if (g_snmpAgent && g_snmpTrapsEnabled) {
> @@ -551,6 +565,7 @@
>      }
>      dr.asynchronous = true;
>      dr.getMutableData() = *dr.ids.d_packet;
> +    dr.ids.d_proxyProtocolPayloadSize = 0;
>      auto query = dnsdist::getInternalQueryFromDQ(dr, false);
>      return dnsdist::queueQueryResumptionEvent(std::move(query));
>    });
> diff -Nru dnsdist-1.9.9/dnsdist-lua.cc dnsdist-1.9.10/dnsdist-lua.cc
> --- dnsdist-1.9.9/dnsdist-lua.cc	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/dnsdist-lua.cc	2025-05-20 11:13:25.000000000 +0200
> @@ -642,7 +642,7 @@
>                           auto ret = std::make_shared<DownstreamState>(std::move(config), std::move(tlsCtx), !(client || configCheck));
>  #ifdef HAVE_XSK
>                           LuaArray<std::shared_ptr<XskSocket>> luaXskSockets;
> -                         if (getOptionalValue<LuaArray<std::shared_ptr<XskSocket>>>(vars, "xskSockets", luaXskSockets) > 0 && !luaXskSockets.empty()) {
> +                         if (!client && !configCheck && getOptionalValue<LuaArray<std::shared_ptr<XskSocket>>>(vars, "xskSockets", luaXskSockets) > 0 && !luaXskSockets.empty()) {
>                             if (g_configurationDone) {
>                               throw std::runtime_error("Adding a server with xsk at runtime is not supported");
>                             }
> @@ -668,6 +668,13 @@
>                           else if (!(client || configCheck)) {
>                             infolog("Added downstream server %s", ret->d_config.remote.toStringWithPort());
>                           }
> +
> +                         if (client || configCheck) {
> +                           /* consume these in client or configuration check mode, to prevent warnings */
> +                           std::string mac;
> +                           getOptionalValue<std::string>(vars, "MACAddr", mac);
> +                           getOptionalValue<LuaArray<std::shared_ptr<XskSocket>>>(vars, "xskSockets", luaXskSockets);
> +                         }
>  #else /* HAVE_XSK */
>        if (!(client || configCheck)) {
>          infolog("Added downstream server %s", ret->d_config.remote.toStringWithPort());
> diff -Nru dnsdist-1.9.9/dnsdist-lua-ffi.cc dnsdist-1.9.10/dnsdist-lua-ffi.cc
> --- dnsdist-1.9.9/dnsdist-lua-ffi.cc	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/dnsdist-lua-ffi.cc	2025-05-20 11:13:25.000000000 +0200
> @@ -121,6 +121,14 @@
>    return dq->dq->ids.origRemote.getPort();
>  }
>  
> +const char* dnsdist_ffi_dnsquestion_get_incoming_interface(const dnsdist_ffi_dnsquestion_t* dnsQuestion)
> +{
> +  if (dnsQuestion == nullptr || dnsQuestion->dq == nullptr || dnsQuestion->dq->ids.cs == nullptr) {
> +    return nullptr;
> +  }
> +  return dnsQuestion->dq->ids.cs->interface.c_str();
> +}
> +
>  void dnsdist_ffi_dnsquestion_get_qname_raw(const dnsdist_ffi_dnsquestion_t* dq, const char** qname, size_t* qnameSize)
>  {
>    const auto& storage = dq->dq->ids.qname.getStorage();
> diff -Nru dnsdist-1.9.9/dnsdist-lua-ffi-interface.h dnsdist-1.9.10/dnsdist-lua-ffi-interface.h
> --- dnsdist-1.9.9/dnsdist-lua-ffi-interface.h	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/dnsdist-lua-ffi-interface.h	2025-05-20 11:13:25.000000000 +0200
> @@ -64,6 +64,7 @@
>  void dnsdist_ffi_dnsquestion_get_remoteaddr(const dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize) __attribute__ ((visibility ("default")));
>  void dnsdist_ffi_dnsquestion_get_masked_remoteaddr(dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize, uint8_t bits) __attribute__ ((visibility ("default")));
>  uint16_t dnsdist_ffi_dnsquestion_get_remote_port(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
> +const char* dnsdist_ffi_dnsquestion_get_incoming_interface(const dnsdist_ffi_dnsquestion_t* dnsQuestion) __attribute__ ((visibility ("default")));
>  void dnsdist_ffi_dnsquestion_get_qname_raw(const dnsdist_ffi_dnsquestion_t* dq, const char** qname, size_t* qnameSize) __attribute__ ((visibility ("default")));
>  size_t dnsdist_ffi_dnsquestion_get_qname_hash(const dnsdist_ffi_dnsquestion_t* dq, size_t init) __attribute__ ((visibility ("default")));
>  uint16_t dnsdist_ffi_dnsquestion_get_qtype(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
> diff -Nru dnsdist-1.9.9/dnsdist-lua-ffi-interface.inc dnsdist-1.9.10/dnsdist-lua-ffi-interface.inc
> --- dnsdist-1.9.9/dnsdist-lua-ffi-interface.inc	2025-04-29 11:46:38.000000000 +0200
> +++ dnsdist-1.9.10/dnsdist-lua-ffi-interface.inc	2025-05-20 11:13:53.000000000 +0200
> @@ -65,6 +65,7 @@
>  void dnsdist_ffi_dnsquestion_get_remoteaddr(const dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize) __attribute__ ((visibility ("default")));
>  void dnsdist_ffi_dnsquestion_get_masked_remoteaddr(dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize, uint8_t bits) __attribute__ ((visibility ("default")));
>  uint16_t dnsdist_ffi_dnsquestion_get_remote_port(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
> +const char* dnsdist_ffi_dnsquestion_get_incoming_interface(const dnsdist_ffi_dnsquestion_t* dnsQuestion) __attribute__ ((visibility ("default")));
>  void dnsdist_ffi_dnsquestion_get_qname_raw(const dnsdist_ffi_dnsquestion_t* dq, const char** qname, size_t* qnameSize) __attribute__ ((visibility ("default")));
>  size_t dnsdist_ffi_dnsquestion_get_qname_hash(const dnsdist_ffi_dnsquestion_t* dq, size_t init) __attribute__ ((visibility ("default")));
>  uint16_t dnsdist_ffi_dnsquestion_get_qtype(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
> diff -Nru dnsdist-1.9.9/dnsdist-proxy-protocol.cc dnsdist-1.9.10/dnsdist-proxy-protocol.cc
> --- dnsdist-1.9.9/dnsdist-proxy-protocol.cc	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/dnsdist-proxy-protocol.cc	2025-05-20 11:13:25.000000000 +0200
> @@ -42,14 +42,19 @@
>    return addProxyProtocol(dq.getMutableData(), payload);
>  }
>  
> -bool addProxyProtocol(DNSQuestion& dq, size_t* payloadSize)
> +bool addProxyProtocol(DNSQuestion& dnsQuestion, size_t* proxyProtocolPayloadSize)
>  {
> -  auto payload = getProxyProtocolPayload(dq);
> -  if (payloadSize != nullptr) {
> -    *payloadSize = payload.size();
> +  auto payload = getProxyProtocolPayload(dnsQuestion);
> +  size_t payloadSize = payload.size();
> +
> +  if (!addProxyProtocol(dnsQuestion, payload)) {
> +    return false;
>    }
>  
> -  return addProxyProtocol(dq, payload);
> +  if (proxyProtocolPayloadSize != nullptr) {
> +    *proxyProtocolPayloadSize = payloadSize;
> +  }
> +  return true;
>  }
>  
>  bool addProxyProtocol(PacketBuffer& buffer, const std::string& payload)
> diff -Nru dnsdist-1.9.9/dnsdist-proxy-protocol.hh dnsdist-1.9.10/dnsdist-proxy-protocol.hh
> --- dnsdist-1.9.9/dnsdist-proxy-protocol.hh	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/dnsdist-proxy-protocol.hh	2025-05-20 11:13:25.000000000 +0200
> @@ -29,7 +29,7 @@
>  
>  std::string getProxyProtocolPayload(const DNSQuestion& dq);
>  
> -bool addProxyProtocol(DNSQuestion& dq, size_t* proxyProtocolPayloadSize = nullptr);
> +bool addProxyProtocol(DNSQuestion& dnsQuestion, size_t* proxyProtocolPayloadSize = nullptr);
>  bool addProxyProtocol(DNSQuestion& dq, const std::string& payload);
>  bool addProxyProtocol(PacketBuffer& buffer, const std::string& payload);
>  bool addProxyProtocol(PacketBuffer& buffer, bool tcp, const ComboAddress& source, const ComboAddress& destination, const std::vector<ProxyProtocolValue>& values);
> diff -Nru dnsdist-1.9.9/dnsdist.service.in dnsdist-1.9.10/dnsdist.service.in
> --- dnsdist-1.9.9/dnsdist.service.in	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/dnsdist.service.in	2025-05-20 11:13:25.000000000 +0200
> @@ -44,7 +44,7 @@
>  ProtectKernelModules=true
>  ProtectKernelTunables=true
>  ProtectSystem=full
> -RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
> +RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK AF_UNIX AF_XDP
>  RestrictNamespaces=true
>  RestrictRealtime=true
>  RestrictSUIDSGID=true
> diff -Nru dnsdist-1.9.9/dnsdist-tcp.cc dnsdist-1.9.10/dnsdist-tcp.cc
> --- dnsdist-1.9.9/dnsdist-tcp.cc	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/dnsdist-tcp.cc	2025-05-20 11:13:25.000000000 +0200
> @@ -114,14 +114,46 @@
>    return t_downstreamTCPConnectionsManager.clear();
>  }
>  
> +static std::pair<std::shared_ptr<TCPConnectionToBackend>, bool> getOwnedDownstreamConnection(std::map<std::shared_ptr<DownstreamState>, std::deque<std::shared_ptr<TCPConnectionToBackend>>>& ownedConnectionsToBackend, const std::shared_ptr<DownstreamState>& backend, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs)
> +{
> +  bool tlvsMismatch = false;
> +  auto connIt = ownedConnectionsToBackend.find(backend);
> +  if (connIt == ownedConnectionsToBackend.end()) {
> +    DEBUGLOG("no owned connection found for " << backend->getName());
> +    return {nullptr, tlvsMismatch};
> +  }
> +
> +  for (auto& conn : connIt->second) {
> +    if (conn->canBeReused(true)) {
> +      if (conn->matchesTLVs(tlvs)) {
> +        DEBUGLOG("Got one owned connection accepting more for " << backend->getName());
> +        conn->setReused();
> +        ++backend->tcpReusedConnections;
> +        return {conn, tlvsMismatch};
> +      }
> +      DEBUGLOG("Found one connection to " << backend->getName() << " but with different TLV values");
> +      tlvsMismatch = true;
> +    }
> +    DEBUGLOG("not accepting more for " << backend->getName());
> +  }
> +
> +  return {nullptr, tlvsMismatch};
> +}
> +
>  std::shared_ptr<TCPConnectionToBackend> IncomingTCPConnectionState::getDownstreamConnection(std::shared_ptr<DownstreamState>& backend, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs, const struct timeval& now)
>  {
> -  auto downstream = getOwnedDownstreamConnection(backend, tlvs);
> +  auto [downstream, tlvsMismatch] = getOwnedDownstreamConnection(d_ownedConnectionsToBackend, backend, tlvs);
>  
>    if (!downstream) {
> +    if (backend->d_config.useProxyProtocol && tlvsMismatch) {
> +      clearOwnedDownstreamConnections(backend);
> +    }
> +
>      /* we don't have a connection to this backend owned yet, let's get one (it might not be a fresh one, though) */
>      downstream = t_downstreamTCPConnectionsManager.getConnectionToDownstream(d_threadData.mplexer, backend, now, std::string());
> -    if (backend->d_config.useProxyProtocol) {
> +    // if we had an existing connection but the TLVs are different, they are likely unique per query so do not bother keeping the connection
> +    // around
> +    if (backend->d_config.useProxyProtocol && !tlvsMismatch) {
>        registerOwnedDownstreamConnection(downstream);
>      }
>    }
> @@ -272,29 +304,32 @@
>    d_state = State::waitingForQuery;
>  }
>  
> -std::shared_ptr<TCPConnectionToBackend> IncomingTCPConnectionState::getOwnedDownstreamConnection(const std::shared_ptr<DownstreamState>& backend, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs)
> +void IncomingTCPConnectionState::registerOwnedDownstreamConnection(std::shared_ptr<TCPConnectionToBackend>& conn)
>  {
> -  auto connIt = d_ownedConnectionsToBackend.find(backend);
> -  if (connIt == d_ownedConnectionsToBackend.end()) {
> -    DEBUGLOG("no owned connection found for " << backend->getName());
> -    return nullptr;
> -  }
> +  const auto& downstream = conn->getDS();
>  
> -  for (auto& conn : connIt->second) {
> -    if (conn->canBeReused(true) && conn->matchesTLVs(tlvs)) {
> -      DEBUGLOG("Got one owned connection accepting more for " << backend->getName());
> -      conn->setReused();
> -      return conn;
> -    }
> -    DEBUGLOG("not accepting more for " << backend->getName());
> -  }
> +  auto& queue = d_ownedConnectionsToBackend[downstream];
> +  // how many proxy-protocol enabled connections do we want to keep around?
> +  // - they are only usable for this incoming connection because of the proxy protocol header containing the source and destination addresses and ports
> +  // - if we have TLV values, and they are unique per query, keeping these is useless
> +  // - if there is no, or identical, TLV values, a single outgoing connection is enough if maxInFlight == 1, or if incoming maxInFlight == outgoing maxInFlight
> +  // so it makes sense to keep a few of them around if incoming maxInFlight is greater than outgoing maxInFlight
>  
> -  return nullptr;
> +  auto incomingMaxInFlightQueriesPerConn = d_ci.cs->d_maxInFlightQueriesPerConn;
> +  incomingMaxInFlightQueriesPerConn = std::max(incomingMaxInFlightQueriesPerConn, static_cast<size_t>(1U));
> +  auto outgoingMaxInFlightQueriesPerConn = downstream->d_config.d_maxInFlightQueriesPerConn;
> +  outgoingMaxInFlightQueriesPerConn = std::max(outgoingMaxInFlightQueriesPerConn, static_cast<size_t>(1U));
> +  size_t maxCachedOutgoingConnections = std::min(static_cast<size_t>(incomingMaxInFlightQueriesPerConn / outgoingMaxInFlightQueriesPerConn), static_cast<size_t>(5U));
> +
> +  queue.push_front(conn);
> +  if (queue.size() > maxCachedOutgoingConnections) {
> +    queue.pop_back();
> +  }
>  }
>  
> -void IncomingTCPConnectionState::registerOwnedDownstreamConnection(std::shared_ptr<TCPConnectionToBackend>& conn)
> +void IncomingTCPConnectionState::clearOwnedDownstreamConnections(const std::shared_ptr<DownstreamState>& downstream)
>  {
> -  d_ownedConnectionsToBackend[conn->getDS()].push_front(conn);
> +  d_ownedConnectionsToBackend.erase(downstream);
>  }
>  
>  /* called when the buffer has been set and the rules have been processed, and only from handleIO (sometimes indirectly via handleQuery) */
> @@ -1053,8 +1088,39 @@
>    return false;
>  }
>  
> +class HandlingIOGuard
> +{
> +public:
> +  HandlingIOGuard(bool& handlingIO) :
> +    d_handlingIO(handlingIO)
> +  {
> +  }
> +  HandlingIOGuard(const HandlingIOGuard&) = delete;
> +  HandlingIOGuard(HandlingIOGuard&&) = delete;
> +  HandlingIOGuard& operator=(const HandlingIOGuard& rhs) = delete;
> +  HandlingIOGuard& operator=(HandlingIOGuard&&) = delete;
> +  ~HandlingIOGuard()
> +  {
> +    d_handlingIO = false;
> +  }
> +
> +private:
> +  bool& d_handlingIO;
> +};
> +
>  void IncomingTCPConnectionState::handleIO()
>  {
> +  // let's make sure we are not already in handleIO() below in the stack:
> +  // this might happen when we have a response available on the backend socket
> +  // right after forwarding the query, and then a query waiting for us on the
> +  // client socket right after forwarding the response, and then a response available
> +  // on the backend socket right after forwarding the query.. you get the idea.
> +  if (d_handlingIO) {
> +    return;
> +  }
> +  d_handlingIO = true;
> +  HandlingIOGuard reentryGuard(d_handlingIO);
> +
>    // why do we loop? Because the TLS layer does buffering, and thus can have data ready to read
>    // even though the underlying socket is not ready, so we need to actually ask for the data first
>    IOState iostate = IOState::Done;
> diff -Nru dnsdist-1.9.9/dnsdist-tcp-upstream.hh dnsdist-1.9.10/dnsdist-tcp-upstream.hh
> --- dnsdist-1.9.9/dnsdist-tcp-upstream.hh	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/dnsdist-tcp-upstream.hh	2025-05-20 11:13:25.000000000 +0200
> @@ -116,9 +116,9 @@
>      return false;
>    }
>  
> -  std::shared_ptr<TCPConnectionToBackend> getOwnedDownstreamConnection(const std::shared_ptr<DownstreamState>& backend, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs);
>    std::shared_ptr<TCPConnectionToBackend> getDownstreamConnection(std::shared_ptr<DownstreamState>& backend, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs, const struct timeval& now);
>    void registerOwnedDownstreamConnection(std::shared_ptr<TCPConnectionToBackend>& conn);
> +  void clearOwnedDownstreamConnections(const std::shared_ptr<DownstreamState>& downstream);
>  
>    static size_t clearAllDownstreamConnections();
>  
> @@ -216,4 +216,5 @@
>    bool d_proxyProtocolPayloadHasTLV{false};
>    bool d_lastIOBlocked{false};
>    bool d_hadErrors{false};
> +  bool d_handlingIO{false};
>  };
> diff -Nru dnsdist-1.9.9/dnsdist-web.cc dnsdist-1.9.10/dnsdist-web.cc
> --- dnsdist-1.9.9/dnsdist-web.cc	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/dnsdist-web.cc	2025-05-20 11:13:25.000000000 +0200
> @@ -913,7 +913,7 @@
>    output << "dnsdist_info{version=\"" << VERSION << "\"} " << "1" << "\n";
>  
>    resp.body = output.str();
> -  resp.headers["Content-Type"] = "text/plain";
> +  resp.headers["Content-Type"] = "text/plain; version=0.0.4";
>  }
>  #endif /* DISABLE_PROMETHEUS */
>  
> diff -Nru dnsdist-1.9.9/doh3.cc dnsdist-1.9.10/doh3.cc
> --- dnsdist-1.9.9/doh3.cc	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/doh3.cc	2025-05-20 11:13:25.000000000 +0200
> @@ -912,14 +912,14 @@
>        if (!quiche_version_is_supported(version)) {
>          DEBUGLOG("Unsupported version");
>          ++frontend.d_doh3UnsupportedVersionErrors;
> -        handleVersionNegociation(sock, clientConnID, serverConnID, client, localAddr, buffer);
> +        handleVersionNegotiation(sock, clientConnID, serverConnID, client, localAddr, buffer, clientState.local.isUnspecified());
>          continue;
>        }
>  
>        if (token_len == 0) {
>          /* stateless retry */
>          DEBUGLOG("No token received");
> -        handleStatelessRetry(sock, clientConnID, serverConnID, client, localAddr, version, buffer);
> +        handleStatelessRetry(sock, clientConnID, serverConnID, client, localAddr, version, buffer, clientState.local.isUnspecified());
>          continue;
>        }
>  
> @@ -966,7 +966,7 @@
>  
>        processH3Events(clientState, frontend, conn->get(), client, serverConnID, buffer);
>  
> -      flushEgress(sock, conn->get().d_conn, client, localAddr, buffer);
> +      flushEgress(sock, conn->get().d_conn, client, localAddr, buffer, clientState.local.isUnspecified());
>      }
>      else {
>        DEBUGLOG("Connection not established");
> @@ -1011,7 +1011,7 @@
>          for (auto conn = frontend->d_server_config->d_connections.begin(); conn != frontend->d_server_config->d_connections.end();) {
>            quiche_conn_on_timeout(conn->second.d_conn.get());
>  
> -          flushEgress(sock, conn->second.d_conn, conn->second.d_peer, conn->second.d_localAddr, buffer);
> +          flushEgress(sock, conn->second.d_conn, conn->second.d_peer, conn->second.d_localAddr, buffer, clientState->local.isUnspecified());
>  
>            if (quiche_conn_is_closed(conn->second.d_conn.get())) {
>  #ifdef DEBUGLOG_ENABLED
> diff -Nru dnsdist-1.9.9/doq.cc dnsdist-1.9.10/doq.cc
> --- dnsdist-1.9.9/doq.cc	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/doq.cc	2025-05-20 11:13:25.000000000 +0200
> @@ -718,14 +718,14 @@
>        if (!quiche_version_is_supported(version)) {
>          DEBUGLOG("Unsupported version");
>          ++frontend.d_doqUnsupportedVersionErrors;
> -        handleVersionNegociation(sock, clientConnID, serverConnID, client, localAddr, buffer);
> +        handleVersionNegotiation(sock, clientConnID, serverConnID, client, localAddr, buffer, clientState.local.isUnspecified());
>          continue;
>        }
>  
>        if (token_len == 0) {
>          /* stateless retry */
>          DEBUGLOG("No token received");
> -        handleStatelessRetry(sock, clientConnID, serverConnID, client, localAddr, version, buffer);
> +        handleStatelessRetry(sock, clientConnID, serverConnID, client, localAddr, version, buffer, clientState.local.isUnspecified());
>          continue;
>        }
>  
> @@ -766,7 +766,7 @@
>          handleReadableStream(frontend, clientState, *conn, streamID, client, serverConnID);
>        }
>  
> -      flushEgress(sock, conn->get().d_conn, client, localAddr, buffer);
> +      flushEgress(sock, conn->get().d_conn, client, localAddr, buffer, clientState.local.isUnspecified());
>      }
>      else {
>        DEBUGLOG("Connection not established");
> @@ -811,7 +811,7 @@
>          for (auto conn = frontend->d_server_config->d_connections.begin(); conn != frontend->d_server_config->d_connections.end();) {
>            quiche_conn_on_timeout(conn->second.d_conn.get());
>  
> -          flushEgress(sock, conn->second.d_conn, conn->second.d_peer, conn->second.d_localAddr, buffer);
> +          flushEgress(sock, conn->second.d_conn, conn->second.d_peer, conn->second.d_localAddr, buffer, clientState->local.isUnspecified());
>  
>            if (quiche_conn_is_closed(conn->second.d_conn.get())) {
>  #ifdef DEBUGLOG_ENABLED
> diff -Nru dnsdist-1.9.9/doq-common.cc dnsdist-1.9.10/doq-common.cc
> --- dnsdist-1.9.9/doq-common.cc	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/doq-common.cc	2025-05-20 11:13:25.000000000 +0200
> @@ -126,10 +126,22 @@
>    }
>  }
>  
> -static void sendFromTo(Socket& sock, const ComboAddress& peer, const ComboAddress& local, PacketBuffer& buffer)
> +static void sendFromTo(Socket& sock, const ComboAddress& peer, const ComboAddress& local, PacketBuffer& buffer, [[maybe_unused]] bool socketBoundToAny)
>  {
> -  const int flags = 0;
> -  if (local.sin4.sin_family == 0) {
> +  /* we only want to specify the source address to use if we were able to
> +     either harvest it from the incoming packet, or if our socket is already
> +     bound to a specific address */
> +  bool setSourceAddress = local.sin4.sin_family != 0;
> +#if defined(__FreeBSD__) || defined(__DragonFly__)
> +  /* FreeBSD and DragonFlyBSD refuse the use of IP_SENDSRCADDR on a socket that is bound to a
> +     specific address, returning EINVAL in that case. */
> +  if (!socketBoundToAny) {
> +    setSourceAddress = false;
> +  }
> +#endif /* __FreeBSD__ || __DragonFly__ */
> +
> +  if (!setSourceAddress) {
> +    const int flags = 0;
>      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
>      auto ret = sendto(sock.getHandle(), buffer.data(), buffer.size(), flags, reinterpret_cast<const struct sockaddr*>(&peer), peer.getSocklen());
>      if (ret < 0) {
> @@ -147,7 +159,7 @@
>    }
>  }
>  
> -void handleStatelessRetry(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, const ComboAddress& localAddr, uint32_t version, PacketBuffer& buffer)
> +void handleStatelessRetry(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, const ComboAddress& localAddr, uint32_t version, PacketBuffer& buffer, bool socketBoundToAny)
>  {
>    auto newServerConnID = getCID();
>    if (!newServerConnID) {
> @@ -170,10 +182,10 @@
>    }
>  
>    buffer.resize(static_cast<size_t>(written));
> -  sendFromTo(sock, peer, localAddr, buffer);
> +  sendFromTo(sock, peer, localAddr, buffer, socketBoundToAny);
>  }
>  
> -void handleVersionNegociation(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, const ComboAddress& localAddr, PacketBuffer& buffer)
> +void handleVersionNegotiation(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, const ComboAddress& localAddr, PacketBuffer& buffer, bool socketBoundToAny)
>  {
>    buffer.resize(MAX_DATAGRAM_SIZE);
>  
> @@ -187,10 +199,10 @@
>    }
>  
>    buffer.resize(static_cast<size_t>(written));
> -  sendFromTo(sock, peer, localAddr, buffer);
> +  sendFromTo(sock, peer, localAddr, buffer, socketBoundToAny);
>  }
>  
> -void flushEgress(Socket& sock, QuicheConnection& conn, const ComboAddress& peer, const ComboAddress& localAddr, PacketBuffer& buffer)
> +void flushEgress(Socket& sock, QuicheConnection& conn, const ComboAddress& peer, const ComboAddress& localAddr, PacketBuffer& buffer, bool socketBoundToAny)
>  {
>    buffer.resize(MAX_DATAGRAM_SIZE);
>    quiche_send_info send_info;
> @@ -206,7 +218,7 @@
>      }
>      // FIXME pacing (as send_info.at should tell us when to send the packet) ?
>      buffer.resize(static_cast<size_t>(written));
> -    sendFromTo(sock, peer, localAddr, buffer);
> +    sendFromTo(sock, peer, localAddr, buffer, socketBoundToAny);
>    }
>  }
>  
> @@ -312,9 +324,7 @@
>         This is indicated by setting the family to 0 which is acted upon
>         in sendUDPResponse() and DelayedPacket::().
>      */
> -    const ComboAddress bogusV4("0.0.0.0:0");
> -    const ComboAddress bogusV6("[::]:0");
> -    if ((localAddr.sin4.sin_family == AF_INET && localAddr == bogusV4) || (localAddr.sin4.sin_family == AF_INET6 && localAddr == bogusV6)) {
> +    if (localAddr.isUnspecified()) {
>        localAddr.sin4.sin_family = 0;
>      }
>    }
> diff -Nru dnsdist-1.9.9/doq-common.hh dnsdist-1.9.10/doq-common.hh
> --- dnsdist-1.9.9/doq-common.hh	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/doq-common.hh	2025-05-20 11:13:25.000000000 +0200
> @@ -92,9 +92,9 @@
>  std::optional<PacketBuffer> getCID();
>  PacketBuffer mintToken(const PacketBuffer& dcid, const ComboAddress& peer);
>  std::optional<PacketBuffer> validateToken(const PacketBuffer& token, const ComboAddress& peer);
> -void handleStatelessRetry(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, const ComboAddress& localAddr, uint32_t version, PacketBuffer& buffer);
> -void handleVersionNegociation(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, const ComboAddress& localAddr, PacketBuffer& buffer);
> -void flushEgress(Socket& sock, QuicheConnection& conn, const ComboAddress& peer, const ComboAddress& localAddr, PacketBuffer& buffer);
> +void handleStatelessRetry(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, const ComboAddress& localAddr, uint32_t version, PacketBuffer& buffer, bool socketBoundToAny);
> +void handleVersionNegotiation(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, const ComboAddress& localAddr, PacketBuffer& buffer, bool socketBoundToAny);
> +void flushEgress(Socket& sock, QuicheConnection& conn, const ComboAddress& peer, const ComboAddress& localAddr, PacketBuffer& buffer, bool socketBoundToAny);
>  void configureQuiche(QuicheConfig& config, const QuicheParams& params, bool isHTTP);
>  bool recvAsync(Socket& socket, PacketBuffer& buffer, ComboAddress& clientAddr, ComboAddress& localAddr);
>  
> diff -Nru dnsdist-1.9.9/iputils.hh dnsdist-1.9.10/iputils.hh
> --- dnsdist-1.9.9/iputils.hh	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/iputils.hh	2025-05-20 11:13:25.000000000 +0200
> @@ -266,6 +266,13 @@
>      return true;
>    }
>  
> +  [[nodiscard]] bool isUnspecified() const
> +  {
> +    const ComboAddress unspecifiedV4("0.0.0.0:0");
> +    const ComboAddress unspecifiedV6("[::]:0");
> +    return *this == unspecifiedV4 || *this == unspecifiedV6;
> +  }
> +
>    ComboAddress mapToIPv4() const
>    {
>      if(!isMappedIPv4())
> diff -Nru dnsdist-1.9.9/noinitvector.hh dnsdist-1.9.10/noinitvector.hh
> --- dnsdist-1.9.9/noinitvector.hh	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/noinitvector.hh	2025-05-20 11:13:25.000000000 +0200
> @@ -1,5 +1,6 @@
>  #pragma once
>  
> +#include <cstdint>
>  #include <memory>
>  #include <new>
>  #include <utility>
> diff -Nru dnsdist-1.9.9/test-dnsdist-lua-ffi.cc dnsdist-1.9.10/test-dnsdist-lua-ffi.cc
> --- dnsdist-1.9.9/test-dnsdist-lua-ffi.cc	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/test-dnsdist-lua-ffi.cc	2025-05-20 11:13:25.000000000 +0200
> @@ -373,6 +373,30 @@
>    BOOST_CHECK_EQUAL(ids.d_protoBufData->d_deviceID, deviceID);
>    BOOST_CHECK_EQUAL(ids.d_protoBufData->d_deviceName, deviceName);
>    BOOST_CHECK_EQUAL(ids.d_protoBufData->d_requestorID, requestorID);
> +
> +  /* no frontend yet */
> +  BOOST_CHECK(dnsdist_ffi_dnsquestion_get_incoming_interface(nullptr) == nullptr);
> +  BOOST_CHECK(dnsdist_ffi_dnsquestion_get_incoming_interface(&lightDQ) == nullptr);
> +  {
> +    /* frontend without and interface set */
> +    const std::string interface{};
> +    ClientState frontend(ids.origDest, false, false, 0, interface, {}, false);
> +    ids.cs = &frontend;
> +    const auto* itfPtr = dnsdist_ffi_dnsquestion_get_incoming_interface(&lightDQ);
> +    BOOST_REQUIRE(itfPtr != nullptr);
> +    BOOST_CHECK_EQUAL(std::string(itfPtr), interface);
> +    ids.cs = nullptr;
> +  }
> +  {
> +    /* frontend with interface set */
> +    const std::string interface{"interface-name-0"};
> +    ClientState frontend(ids.origDest, false, false, 0, interface, {}, false);
> +    ids.cs = &frontend;
> +    const auto* itfPtr = dnsdist_ffi_dnsquestion_get_incoming_interface(&lightDQ);
> +    BOOST_REQUIRE(itfPtr != nullptr);
> +    BOOST_CHECK_EQUAL(std::string(itfPtr), interface);
> +    ids.cs = nullptr;
> +  }
>  }
>  
>  BOOST_AUTO_TEST_CASE(test_Response)
> diff -Nru dnsdist-1.9.9/test-dnsdisttcp_cc.cc dnsdist-1.9.10/test-dnsdisttcp_cc.cc
> --- dnsdist-1.9.9/test-dnsdisttcp_cc.cc	2025-04-29 11:46:04.000000000 +0200
> +++ dnsdist-1.9.10/test-dnsdisttcp_cc.cc	2025-05-20 11:13:25.000000000 +0200
> @@ -4182,4 +4182,82 @@
>    }
>  }
>  
> +BOOST_FIXTURE_TEST_CASE(test_Pipelined_Queries_Immediate_Responses, TestFixture)
> +{
> +  auto local = getBackendAddress("1", 80);
> +  ClientState localCS(local, true, false, 0, "", {}, true);
> +  auto tlsCtx = std::make_shared<MockupTLSCtx>();
> +  localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
> +
> +  TCPClientThreadData threadData;
> +  threadData.mplexer = std::make_unique<MockupFDMultiplexer>();
> +
> +  timeval now{};
> +  gettimeofday(&now, nullptr);
> +
> +  PacketBuffer query;
> +  GenericDNSPacketWriter<PacketBuffer> pwQ(query, DNSName("powerdns.com."), QType::A, QClass::IN, 0);
> +  pwQ.getHeader()->rd = 1;
> +  pwQ.getHeader()->id = 0;
> +
> +  auto querySize = static_cast<uint16_t>(query.size());
> +  const std::array<uint8_t, 2> sizeBytes{ static_cast<uint8_t>(querySize / 256), static_cast<uint8_t>(querySize % 256) };
> +  query.insert(query.begin(), sizeBytes.begin(), sizeBytes.end());
> +
> +  auto backend = std::make_shared<DownstreamState>(getBackendAddress("42", 53));
> +  backend->d_tlsCtx = tlsCtx;
> +
> +  {
> +    /* 1000 queries from client passed to backend (one at at time), backend answers right away */
> +    TEST_INIT("=> Query to backend, backend answers right away");
> +    const size_t nbQueries = 10000;
> +    s_readBuffer = query;
> +    s_backendReadBuffer = query;
> +
> +    s_steps = {
> +      { ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done },
> +      { ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 2 },
> +      { ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, query.size() - 2 },
> +      /* opening a connection to the backend */
> +      { ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done },
> +      { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, query.size() },
> +      { ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, 2 },
> +      { ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, query.size() - 2 },
> +      { ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, query.size() },
> +    };
> +    for (size_t idx = 1; idx < nbQueries; idx++) {
> +      appendPayloadEditingID(s_readBuffer, query, idx);
> +      appendPayloadEditingID(s_backendReadBuffer, query, idx);
> +
> +      s_steps.emplace_back(ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 2);
> +      s_steps.emplace_back(ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, query.size() - 2);
> +      s_steps.emplace_back(ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, query.size());
> +      s_steps.emplace_back(ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, 2);
> +      s_steps.emplace_back(ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, query.size() - 2);
> +      s_steps.emplace_back(ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, query.size());
> +    }
> +    s_steps.emplace_back(ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 0);
> +    /* closing client connection */
> +    s_steps.emplace_back(ExpectedStep::ExpectedRequest::closeClient, IOState::Done);
> +    /* closing a connection to the backend */
> +    s_steps.emplace_back(ExpectedStep::ExpectedRequest::closeBackend, IOState::Done);
> +
> +    s_processQuery = [backend](DNSQuestion&, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
> +      selectedBackend = backend;
> +      return ProcessQueryResult::PassToBackend;
> +    };
> +    s_processResponse = [](PacketBuffer&, DNSResponse&, bool) -> bool {
> +      return true;
> +    };
> +
> +    auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
> +    state->handleIO();
> +    BOOST_CHECK_EQUAL(s_writeBuffer.size(), query.size() * nbQueries);
> +    BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size() * nbQueries);
> +    BOOST_CHECK_EQUAL(backend->outstanding.load(), 0U);
> +    /* we need to clear them now, otherwise we end up with dangling pointers to the steps via the TLS context, etc */
> +    IncomingTCPConnectionState::clearAllDownstreamConnections();
> +  }
> +}
> +
>  BOOST_AUTO_TEST_SUITE_END();


-- 
Sebastian Ramacher


Reply to: