]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: add basic DoQ support
authorCharles-Henri Bruyand <charles-henri.bruyand@open-xchange.com>
Mon, 31 Jul 2023 13:39:36 +0000 (15:39 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 9 Oct 2023 11:36:45 +0000 (13:36 +0200)
pdns/dnsdist-console.cc
pdns/dnsdist-idstate.hh
pdns/dnsdist-lua.cc
pdns/dnsdist.cc
pdns/dnsdist.hh
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/dnsdist-internal-queries.cc
pdns/dnsdistdist/docs/reference/config.rst
pdns/dnsdistdist/doq.cc [new file with mode: 0644]
pdns/dnsdistdist/doq.hh [new file with mode: 0644]

index d1a42663d1309e22ee9ddad62633557332de905c..a451f31e1ad7b4b899e6a56afc794b1eff1833d7 100644 (file)
@@ -470,6 +470,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "addConsoleACL", true, "netmask", "add a netmask to the console ACL" },
   { "addDNSCryptBind", true, "\"127.0.0.1:8443\", \"provider name\", \"/path/to/resolver.cert\", \"/path/to/resolver.key\", {reusePort=false, tcpFastOpenQueueSize=0, interface=\"\", cpus={}}", "listen to incoming DNSCrypt queries on 127.0.0.1 port 8443, with a provider name of `provider name`, using a resolver certificate and associated key stored respectively in the `resolver.cert` and `resolver.key` files. The fifth optional parameter is a table of parameters" },
   { "addDOHLocal", true, "addr, certFile, keyFile [, urls [, vars]]", "listen to incoming DNS over HTTPS queries on the specified address using the specified certificate and key. The last two parameters are tables" },
+  { "addDOQLocal", true, "addr, certFile, keyFile [, vars]", "listen to incoming DNS over QUIC queries on the specified address using the specified certificate and key. The last parameter is a table" },
   { "addDynBlocks", true, "addresses, message[, seconds[, action]]", "block the set of addresses with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" },
   { "addDynBlockSMT", true, "names, message[, seconds [, action]]", "block the set of names with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" },
   { "addLocal", true, "addr [, {doTCP=true, reusePort=false, tcpFastOpenQueueSize=0, interface=\"\", cpus={}}]", "add `addr` to the list of addresses we listen on" },
@@ -746,6 +747,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "showDNSCryptBinds", true, "", "display the currently configured DNSCrypt binds" },
   { "showDOHFrontends", true, "", "list all the available DOH frontends" },
   { "showDOHResponseCodes", true, "", "show the HTTP response code statistics for the DoH frontends"},
+  { "showDOQFrontends", true, "", "list all the available DOQ frontends" },
   { "showDynBlocks", true, "", "show dynamic blocks in force" },
   { "showPools", true, "", "show the available pools" },
   { "showPoolServerPolicy", true, "pool", "show server selection policy for this pool" },
index 313e434ea8252cbccc25470184802418ec599f6d..ddc69475401d62f8a74df927b3f9ada0e6de4f6c 100644 (file)
@@ -32,6 +32,7 @@
 
 struct ClientState;
 struct DOHUnitInterface;
+struct DOQUnit;
 class DNSCryptQuery;
 class DNSDistPacketCache;
 
@@ -130,9 +131,11 @@ struct InternalQueryState
   std::unique_ptr<ProtoBufData> d_protoBufData{nullptr};
   boost::optional<uint32_t> tempFailureTTL{boost::none}; // 8
   ClientState* cs{nullptr}; // 8
+
   std::unique_ptr<DOHUnitInterface> du; // 8
   size_t d_proxyProtocolPayloadSize{0}; // 8
   int32_t d_streamID{-1}; // 4
+  std::unique_ptr<DOQUnit> doqu{nullptr}; // 8
   uint32_t cacheKey{0}; // 4
   uint32_t cacheKeyNoECS{0}; // 4
   // DoH-only */
index 902ccf64ac0c7dfcf9ac8610d9012aaa52a5404a..39a30ba053c38a8db5cf524e1058b2880456e230 100644 (file)
@@ -2483,6 +2483,89 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 #endif /* HAVE_DNS_OVER_HTTPS */
   });
 
+  luaCtx.writeFunction("addDOQLocal", [client](const std::string& addr, boost::optional<boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>>> certFiles, boost::optional<boost::variant<std::string, LuaArray<std::string>>> keyFiles, boost::optional<localbind_t> vars) {
+    if (client) {
+      return;
+    }
+#ifdef HAVE_DNS_OVER_QUIC
+    if (!checkConfigurationTime("addDOQLocal")) {
+      return;
+    }
+    setLuaSideEffect();
+
+    auto frontend = std::make_shared<DOQFrontend>();
+    if (!loadTLSCertificateAndKeys("addDOQLocal", frontend->d_tlsConfig.d_certKeyPairs, *certFiles, *keyFiles)) {
+      return;
+    }
+    frontend->d_local = ComboAddress(addr, 853);
+
+    bool reusePort = false;
+    int tcpFastOpenQueueSize = 0;
+    int tcpListenQueueSize = 0;
+    uint64_t maxInFlightQueriesPerConn = 0;
+    uint64_t tcpMaxConcurrentConnections = 0;
+    std::string interface;
+    std::set<int> cpus;
+    std::vector<std::pair<ComboAddress, int>> additionalAddresses;
+
+    if (vars) {
+      parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections);
+//      getOptionalValue<int>(vars, "idleTimeout", frontend->d_idleTimeout);
+
+      getOptionalValue<int>(vars, "internalPipeBufferSize", frontend->d_internalPipeBufferSize);
+
+      parseTLSConfig(frontend->d_tlsConfig, "addDOQLocal", vars);
+
+      bool ignoreTLSConfigurationErrors = false;
+      if (getOptionalValue<bool>(vars, "ignoreTLSConfigurationErrors", ignoreTLSConfigurationErrors) > 0 && ignoreTLSConfigurationErrors) {
+        // we are asked to try to load the certificates so we can return a potential error
+        // and properly ignore the frontend before actually launching it
+        try {
+          std::map<int, std::string> ocspResponses = {};
+          auto ctx = libssl_init_server_context(frontend->d_tlsConfig, ocspResponses);
+        }
+        catch (const std::runtime_error& e) {
+          errlog("Ignoring DoQ frontend: '%s'", e.what());
+          return;
+        }
+      }
+
+      checkAllParametersConsumed("addDOQLocal", vars);
+    }
+    g_doqlocals.push_back(frontend);
+    auto cs = std::make_unique<ClientState>(frontend->d_local, false, reusePort, tcpFastOpenQueueSize, interface, cpus);
+    cs->doqFrontend = frontend;
+    cs->d_additionalAddresses = std::move(additionalAddresses);
+
+    g_frontends.push_back(std::move(cs));
+#else
+      throw std::runtime_error("addDOQLocal() called but DNS over QUIC support is not present!");
+#endif
+  });
+
+  luaCtx.writeFunction("showDOQFrontends", []() {
+#ifdef HAVE_DNS_OVER_QUIC
+    setLuaNoSideEffect();
+    try {
+      ostringstream ret;
+      boost::format fmt("%-3d %-20.20s");
+      ret << (fmt % "#" % "Address") << endl;
+      size_t counter = 0;
+      for (const auto& ctx : g_doqlocals) {
+        ret << (fmt % counter % ctx->d_local.toStringWithPort()) << endl;
+        counter++;
+      }
+      g_outputBuffer = ret.str();
+    }
+    catch (const std::exception& e) {
+      g_outputBuffer = e.what();
+      throw;
+    }
+#else
+      g_outputBuffer = "DNS over QUIC support is not present!\n";
+#endif
+  });
+
   luaCtx.writeFunction("showDOHFrontends", []() {
 #ifdef HAVE_DNS_OVER_HTTPS
     setLuaNoSideEffect();
index 7d6e5bac60bef21ef7b5515e9b7e598be694b137..d2b7759968b3094f0dd9bccea2dc37f7fd386c6f 100644 (file)
@@ -109,6 +109,7 @@ string g_outputBuffer;
 
 std::vector<std::shared_ptr<TLSFrontend>> g_tlslocals;
 std::vector<std::shared_ptr<DOHFrontend>> g_dohlocals;
+std::vector<std::shared_ptr<DOQFrontend>> g_doqlocals;
 std::vector<std::shared_ptr<DNSCryptContext>> g_dnsCryptLocals;
 
 shared_ptr<BPFFilter> g_defaultBPFFilter{nullptr};
@@ -2317,6 +2318,10 @@ static void setUpLocalBind(std::unique_ptr<ClientState>& cstate)
       else {
         infolog("Listening on %s", addr.toStringWithPort());
       }
+    } else {
+      if (cs.doqFrontend != nullptr) {
+        infolog("Listening on %s for DoQ", addr.toStringWithPort());
+      }
     }
   };
 
@@ -2341,6 +2346,9 @@ static void setUpLocalBind(std::unique_ptr<ClientState>& cstate)
   if (cstate->dohFrontend != nullptr) {
     cstate->dohFrontend->setup();
   }
+  if (cstate->doqFrontend != nullptr) {
+    cstate->doqFrontend->setup();
+  }
 
   cstate->ready = true;
 }
@@ -2728,7 +2736,7 @@ int main(int argc, char** argv)
     if (!g_cmdLine.locals.empty()) {
       for (auto it = g_frontends.begin(); it != g_frontends.end(); ) {
         /* DoH, DoT and DNSCrypt frontends are separate */
-        if ((*it)->dohFrontend == nullptr && (*it)->tlsFrontend == nullptr && (*it)->dnscryptCtx == nullptr) {
+        if ((*it)->dohFrontend == nullptr && (*it)->tlsFrontend == nullptr && (*it)->dnscryptCtx == nullptr && (*it)->doqFrontend == nullptr) {
           it = g_frontends.erase(it);
         }
         else {
@@ -2927,6 +2935,16 @@ int main(int argc, char** argv)
 #endif /* HAVE_DNS_OVER_HTTPS */
         continue;
       }
+      if (cs->doqFrontend != nullptr) {
+#ifdef HAVE_DNS_OVER_QUIC
+        std::thread t1(doqThread, cs.get());
+        if (!cs->cpus.empty()) {
+          mapThreadToCPUList(t1.native_handle(), cs->cpus);
+        }
+        t1.detach();
+#endif /* HAVE_DNS_OVER_QUIC */
+        continue;
+      }
       if (cs->udpFD >= 0) {
 #ifdef USE_SINGLE_ACCEPTOR_THREAD
         udpStates.push_back(cs.get());
index 694dab3a6cbeb74328c218ee5a50370809fb07dc..f9455f2d11e6260fe5ecfa23a4ce0f14da58ad48 100644 (file)
@@ -20,6 +20,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #pragma once
+
 #include "config.h"
 #include "ext/luawrapper/include/LuaContext.hpp"
 
@@ -43,6 +44,7 @@
 #include "dnsdist-protocols.hh"
 #include "dnsname.hh"
 #include "dnsdist-doh-common.hh"
+#include "doq.hh"
 #include "ednsoptions.hh"
 #include "iputils.hh"
 #include "misc.hh"
@@ -492,6 +494,7 @@ struct ClientState
   std::shared_ptr<DNSCryptContext> dnscryptCtx{nullptr};
   std::shared_ptr<TLSFrontend> tlsFrontend{nullptr};
   std::shared_ptr<DOHFrontend> dohFrontend{nullptr};
+  std::shared_ptr<DOQFrontend> doqFrontend{nullptr};
   std::shared_ptr<BPFFilter> d_filter{nullptr};
   size_t d_maxInFlightQueriesPerConn{1};
   size_t d_tcpConcurrentConnectionsLimit{0};
@@ -1053,6 +1056,7 @@ extern ComboAddress g_serverControl; // not changed during runtime
 
 extern std::vector<shared_ptr<TLSFrontend>> g_tlslocals;
 extern std::vector<shared_ptr<DOHFrontend>> g_dohlocals;
+extern std::vector<shared_ptr<DOQFrontend>> g_doqlocals;
 extern std::vector<std::unique_ptr<ClientState>> g_frontends;
 extern bool g_truncateTC;
 extern bool g_fixupCase;
@@ -1099,6 +1103,10 @@ struct LocalHolders
 
 void tcpAcceptorThread(std::vector<ClientState*> states);
 
+#ifdef HAVE_DNS_OVER_QUIC
+void doqThread(ClientState* cs);
+#endif /* HAVE_DNS_OVER_QUIC */
+
 void setLuaNoSideEffect(); // if nothing has been declared, set that there are no side effects
 void setLuaSideEffect();   // set to report a side effect, cancelling all _no_ side effect calls
 bool getLuaNoSideEffect(); // set if there were only explicit declarations of _no_ side effect
index db92cc87305d05d5b9e7dd4f72aa71c63c48add0..411d3380010247ec1ca9a253ba9cada4e742884a 100644 (file)
@@ -211,6 +211,7 @@ dnsdist_SOURCES = \
        dnstap.cc dnstap.hh \
        dnswriter.cc dnswriter.hh \
        doh.hh \
+       doq.hh \
        dolog.hh \
        ednscookies.cc ednscookies.hh \
        ednsoptions.cc ednsoptions.hh \
@@ -436,6 +437,12 @@ endif
 
 endif
 
+if HAVE_DNS_OVER_QUIC
+
+dnsdist_SOURCES += doq.cc
+
+endif
+
 if HAVE_NGHTTP2
 dnsdist_LDADD += $(NGHTTP2_LDFLAGS) $(NGHTTP2_LIBS)
 testrunner_LDADD += $(NGHTTP2_LDFLAGS) $(NGHTTP2_LIBS)
index ea4c5413970946d56f885862baa8df019260429e..9f6a3c40d390f918403b1da13694cc6784117a76 100644 (file)
@@ -23,6 +23,7 @@
 #include "dnsdist-nghttp2-in.hh"
 #include "dnsdist-tcp.hh"
 #include "doh.hh"
+#include "doq.hh"
 
 std::unique_ptr<CrossProtocolQuery> getUDPCrossProtocolQueryFromDQ(DNSQuestion& dq);
 
@@ -43,6 +44,11 @@ std::unique_ptr<CrossProtocolQuery> getInternalQueryFromDQ(DNSQuestion& dq, bool
 #endif /* HAVE_LIBH2OEVLOOP */
     return getTCPCrossProtocolQueryFromDQ(dq);
   }
+#endif
+#ifdef HAVE_DNS_OVER_QUIC
+  else if (protocol == dnsdist::Protocol::DoQ) {
+    return getDOQCrossProtocolQueryFromDQ(dq, isResponse);
+  }
 #endif
   else {
     return getTCPCrossProtocolQueryFromDQ(dq);
index cca41b76f00bc1db81cc32862b4a470485fd074b..fbb8e73f77645ce30b9a5aa269760b2c5bffe0c4 100644 (file)
@@ -171,6 +171,26 @@ Listen Sockets
   * ``readAhead``: bool - When the TLS provider is set to OpenSSL, whether we tell the library to read as many input bytes as possible, which leads to better performance by reducing the number of syscalls. Default is true.
   * ``proxyProtocolOutsideTLS``: bool - When the use of incoming proxy protocol is enabled, whether the payload is prepended after the start of the TLS session (so inside, meaning it is protected by the TLS layer providing encryption and authentication) or not (outside, meaning it is in clear-text). Default is false which means inside. Note that most third-party software like HAproxy expect the proxy protocol payload to be outside, in clear-text.
 
+.. function:: addDOQLocal(address, [certFile(s) [, keyFile(s) [, urls [, options]]]])
+
+  .. versionadded:: 1.9.0
+
+  Listen on the specified address and UDP port for incoming DNS over QUIC connections, presenting the specified X.509 certificate.
+
+  :param str address: The IP Address with an optional port to listen on.
+                      The default port is 853.
+  :param str certFile(s): The path to a X.509 certificate file in PEM format, a list of paths to such files, or a TLSCertificate object.
+  :param str keyFile(s): The path to the private key file corresponding to the certificate, or a list of paths to such files, whose order should match the certFile(s) ones. Ignored if ``certFile`` contains TLSCertificate objects.
+  :param table options: A table with key: value pairs with listen options.
+
+  Options:
+
+  * ``reusePort=false``: bool - Set the ``SO_REUSEPORT`` socket option.
+  * ``interface=""``: str - Set the network interface to use.
+  * ``cpus={}``: table - Set the CPU affinity for this listener thread, asking the scheduler to run it on a single CPU id, or a set of CPU ids. This parameter is only available if the OS provides the pthread_setaffinity_np() function.
+  * ``idleTimeout=30``: int - Set the idle timeout, in seconds.
+  * ``internalPipeBufferSize=0``: int - Set the size in bytes of the internal buffer of the pipes used internally to pass queries and responses between threads. Requires support for ``F_SETPIPE_SZ`` which is present in Linux since 2.6.35. The actual size might be rounded up to a multiple of a page size. 0 means that the OS default size is used. The default value is 0, except on Linux where it is 1048576 since 1.6.0.
+
 .. function:: addTLSLocal(address, certFile(s), keyFile(s) [, options])
 
   .. versionchanged:: 1.4.0
@@ -1220,6 +1240,12 @@ Status, Statistics and More
 
   Print the HTTP response codes statistics for all available DNS over HTTPS frontends.
 
+.. function:: showDOQFrontends()
+
+  .. versionadded:: 1.9.0
+
+  Print the list of all available DNS over QUIC frontends.
+
 .. function:: showResponseLatency()
 
   Show a plot of the response time latency distribution
diff --git a/pdns/dnsdistdist/doq.cc b/pdns/dnsdistdist/doq.cc
new file mode 100644 (file)
index 0000000..c8e8bbe
--- /dev/null
@@ -0,0 +1,864 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "doq.hh"
+
+#include "dnsdist-tcp.hh"
+#include "dolog.hh"
+#include "iputils.hh"
+#include "misc.hh"
+#include "sstuff.hh"
+#include "dnsparser.hh"
+#include "threadname.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsdist-proxy-protocol.hh"
+
+
+class DOQServerConfig
+{
+public:
+  DOQServerConfig(std::unique_ptr<quiche_config, decltype(&quiche_config_free)>&& config_, uint32_t internalPipeBufferSize) :
+    config(std::move(config_))
+  {
+    {
+      auto [sender, receiver] = pdns::channel::createObjectQueue<DOQUnit>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverBlocking, internalPipeBufferSize);
+      d_querySender = std::move(sender);
+      d_queryReceiver = std::move(receiver);
+    }
+    {
+      auto [sender, receiver] = pdns::channel::createObjectQueue<DOQUnit>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverBlocking, internalPipeBufferSize);
+      d_responseSender = std::move(sender);
+      d_responseReceiver = std::move(receiver);
+    }
+  }
+  DOQServerConfig(const DOQServerConfig&) = delete;
+  DOQServerConfig(DOQServerConfig&&) = default;
+  DOQServerConfig& operator=(const DOQServerConfig&) = delete;
+  DOQServerConfig& operator=(DOQServerConfig&&) = default;
+  ~DOQServerConfig() = default;
+
+  LocalHolders holders;
+  QuicheConfig config;
+  ClientState* cs{nullptr};
+  std::shared_ptr<DOQFrontend> df{nullptr};
+  pdns::channel::Sender<DOQUnit> d_querySender;
+  pdns::channel::Receiver<DOQUnit> d_queryReceiver;
+  pdns::channel::Sender<DOQUnit> d_responseSender;
+  pdns::channel::Receiver<DOQUnit> d_responseReceiver;
+};
+
+#if 1
+#define DEBUGLOG_ENABLED
+#define DEBUGLOG(x) std::cerr<<x<<std::endl;
+#else
+#define DEBUGLOG(x)
+#endif
+
+static constexpr size_t MAX_DATAGRAM_SIZE = 1350;
+static constexpr size_t LOCAL_CONN_ID_LEN = 16;
+static constexpr size_t TOKEN_LEN = 32; /* check if this needs to be authenticated, via HMAC-SHA256, for example, see rfc9000 section 8.1.1 */
+
+static std::map<PacketBuffer, Connection> s_connections;
+
+/* This internal function sends back the object to the main thread to send a reply.
+   The caller should NOT release or touch the unit after calling this function */
+static void sendDOQUnitToTheMainThread(DOQUnitUniquePtr&& du, const char* description)
+{
+  if (du->responseSender == nullptr) {
+    return;
+  }
+  try {
+    if (!du->responseSender->send(std::move(du))) {
+      vinfolog("Unable to pass a %s to the DoQ worker thread because the pipe is full", description);
+    }
+  } catch (const std::exception& e) {
+    vinfolog("Unable to pass a %s to the DoQ worker thread because we couldn't write to the pipe: %s", description, e.what());
+  }
+}
+
+class DOQTCPCrossQuerySender final : public TCPQuerySender
+{
+public:
+  DOQTCPCrossQuerySender()
+  {
+  }
+
+  bool active() const override
+  {
+    return true;
+  }
+
+  void handleResponse(const struct timeval& now, TCPResponse&& response) override
+  {
+    if (!response.d_idstate.doqu) {
+      return;
+    }
+
+    auto du = std::move(response.d_idstate.doqu);
+    if (du->responseSender == nullptr) {
+      return;
+    }
+
+    du->response = std::move(response.d_buffer);
+    du->ids = std::move(response.d_idstate);
+    DNSResponse dr(du->ids, du->response, du->downstream);
+
+    dnsheader cleartextDH;
+    memcpy(&cleartextDH, dr.getHeader(), sizeof(cleartextDH));
+
+    if (!response.isAsync()) {
+
+      static thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localRespRuleActions = g_respruleactions.getLocal();
+      static thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localCacheInsertedRespRuleActions = g_cacheInsertedRespRuleActions.getLocal();
+
+      dr.ids.doqu = std::move(du);
+
+
+      if (!processResponse(dr.ids.doqu->response, *localRespRuleActions, *localCacheInsertedRespRuleActions, dr, false)) {
+        if (dr.ids.doqu) {
+
+          sendDOQUnitToTheMainThread(std::move(dr.ids.doqu), "Response dropped by rules");
+        }
+        return;
+      }
+
+      if (dr.isAsynchronous()) {
+        return;
+      }
+
+      du = std::move(dr.ids.doqu);
+    }
+
+    if (!du->ids.selfGenerated) {
+      double udiff = du->ids.queryRealTime.udiff();
+      vinfolog("Got answer from %s, relayed to %s (quic), took %f us", du->downstream->d_config.remote.toStringWithPort(), du->ids.origRemote.toStringWithPort(), udiff);
+
+      auto backendProtocol = du->downstream->getProtocol();
+      if (backendProtocol == dnsdist::Protocol::DoUDP && du->tcp) {
+        backendProtocol = dnsdist::Protocol::DoTCP;
+      }
+      handleResponseSent(du->ids, udiff, du->ids.origRemote, du->downstream->d_config.remote, du->response.size(), cleartextDH, backendProtocol, true);
+    }
+
+    ++dnsdist::metrics::g_stats.responses;
+    if (du->ids.cs) {
+      ++du->ids.cs->responses;
+    }
+
+    sendDOQUnitToTheMainThread(std::move(du), "cross-protocol response");
+  }
+
+  void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override
+  {
+    return handleResponse(now, std::move(response));
+  }
+
+  void notifyIOError(const struct timeval& now, TCPResponse&& response) override
+  {
+    // auto& query = response.d_idstate;
+    // if (!query.du) {
+    //   return;
+    // }
+
+    // auto dohUnit = getDUFromIDS(query);
+    // if (dohUnit->responseSender == nullptr) {
+    //   return;
+    // }
+
+    // du->ids = std::move(query);
+    // sendDOQUnitToTheMainThread(std::move(du), "cross-protocol error response");
+  }
+ // void notifyIOError(InternalQueryState&& query, const struct timeval& now) override
+  // {
+  //   if (!query.doqu) {
+  //     return;
+  //   }
+
+  //   if (query.doqu->responseSender == nullptr) {
+  //     return;
+  //   }
+
+  //   auto du = std::move(query.doqu);
+  //   du->ids = std::move(query);
+  //   sendDOQUnitToTheMainThread(std::move(du), "cross-protocol error response");
+  // }
+};
+
+class DOQCrossProtocolQuery : public CrossProtocolQuery
+{
+public:
+  DOQCrossProtocolQuery(DOQUnitUniquePtr&& du, bool isResponse)
+  {
+    if (isResponse) {
+      /* happens when a response becomes async */
+      query = InternalQuery(std::move(du->response), std::move(du->ids));
+    }
+    else {
+      /* we need to duplicate the query here because we might need
+         the existing query later if we get a truncated answer */
+      query = InternalQuery(PacketBuffer(du->query), std::move(du->ids));
+    }
+
+    /* it might have been moved when we moved du->ids */
+    if (du) {
+      query.d_idstate.doqu = std::move(du);
+    }
+
+    /* we _could_ remove it from the query buffer and put in query's d_proxyProtocolPayload,
+       clearing query.d_proxyProtocolPayloadAdded and du->proxyProtocolPayloadSize.
+       Leave it for now because we know that the onky case where the payload has been
+       added is when we tried over UDP, got a TC=1 answer and retried over TCP/DoT,
+       and we know the TCP/DoT code can handle it. */
+    query.d_proxyProtocolPayloadAdded = query.d_idstate.doqu->proxyProtocolPayloadSize > 0;
+    downstream = query.d_idstate.doqu->downstream;
+  }
+
+  void handleInternalError()
+  {
+    sendDOQUnitToTheMainThread(std::move(query.d_idstate.doqu), "DOQ internal error");
+  }
+
+  std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
+  {
+    query.d_idstate.doqu->downstream = downstream;
+    return s_sender;
+  }
+
+  DNSQuestion getDQ() override
+  {
+    auto& ids = query.d_idstate;
+    DNSQuestion dq(ids, query.d_buffer);
+    return dq;
+  }
+
+  DNSResponse getDR() override
+  {
+    auto& ids = query.d_idstate;
+    DNSResponse dr(ids, query.d_buffer, downstream);
+    return dr;
+   }
+
+  DOQUnitUniquePtr&& releaseDU()
+  {
+    return std::move(query.d_idstate.doqu);
+  }
+
+private:
+  static std::shared_ptr<DOQTCPCrossQuerySender> s_sender;
+};
+
+std::shared_ptr<DOQTCPCrossQuerySender> DOQCrossProtocolQuery::s_sender = std::make_shared<DOQTCPCrossQuerySender>();
+
+/* Always called from the main DoQ thread */
+static void handleResponse(DOQFrontend& df, Connection& conn, const uint64_t streamID, const PacketBuffer& response)
+{
+  uint16_t responseSize = static_cast<uint16_t>(response.size());
+  const uint8_t sizeBytes[] = { static_cast<uint8_t>(responseSize / 256), static_cast<uint8_t>(responseSize % 256) };
+  auto res = quiche_conn_stream_send(conn.d_conn.get(), streamID, sizeBytes, sizeof(sizeBytes), false);
+  if (res == sizeof(sizeBytes)) {
+    res = quiche_conn_stream_send(conn.d_conn.get(), streamID, response.data(), response.size(), true);
+  }
+}
+
+void DOQFrontend::setup()
+{
+  auto config = QuicheConfig(quiche_config_new(QUICHE_PROTOCOL_VERSION), quiche_config_free);
+  for (const auto& pair : d_tlsConfig.d_certKeyPairs) {
+    auto res = quiche_config_load_cert_chain_from_pem_file(config.get(), pair.d_cert.c_str());
+    if (res != 0) {
+      throw std::runtime_error("Error loading the server certificate: " + std::to_string(res));
+    }
+    if (pair.d_key) {
+      res = quiche_config_load_priv_key_from_pem_file(config.get(), pair.d_key->c_str());
+      if (res != 0) {
+        throw std::runtime_error("Error loading the server key: " + std::to_string(res));
+      }
+    }
+  }
+
+  {
+    const std::array<uint8_t, 4> alpn{'\x03', 'd', 'o', 'q'};
+    auto res = quiche_config_set_application_protos(config.get(),
+                                                    alpn.data(),
+                                                    alpn.size());
+    if (res != 0) {
+      throw std::runtime_error("Error setting ALPN: " + std::to_string(res));
+    }
+  }
+
+  quiche_config_set_max_idle_timeout(config.get(), 5000);
+  quiche_config_set_max_recv_udp_payload_size(config.get(), MAX_DATAGRAM_SIZE);
+  quiche_config_set_max_send_udp_payload_size(config.get(), MAX_DATAGRAM_SIZE);
+  quiche_config_set_initial_max_data(config.get(), 10000000);
+  quiche_config_set_initial_max_stream_data_bidi_local(config.get(), 1000000);
+  quiche_config_set_initial_max_stream_data_bidi_remote(config.get(), 1000000);
+  quiche_config_set_initial_max_streams_bidi(config.get(), 100);
+  quiche_config_set_cc_algorithm(config.get(), QUICHE_CC_RENO);
+  // quiche_config_log_keys(config.get());
+
+  d_server_config = std::make_shared<DOQServerConfig>(std::move(config), d_internalPipeBufferSize);
+}
+
+static std::optional<PacketBuffer> getCID()
+{
+  // FIXME remplacer par notre truc de random
+  int rng = open("/dev/urandom", O_RDONLY);
+  if (rng < 0) {
+    return std::nullopt;
+
+  }
+  PacketBuffer buffer;
+  buffer.resize(LOCAL_CONN_ID_LEN);
+  auto got = read(rng, buffer.data(), LOCAL_CONN_ID_LEN);
+  if (got < 0) {
+    return std::nullopt;
+  }
+
+  return buffer;
+}
+
+static PacketBuffer mintToken(const PacketBuffer& dcid, const ComboAddress& peer)
+{
+  // FIXME: really check whether this needs to be authenticated, via HMAC for example
+  // client recoit un datagram
+  // challenge avec token
+  // suffisement d'infos pour binder a la bonne adresse
+  // filer l'original CID fille par le client.
+  // -> ne pas garder l'etat
+  // -> inclure l'info dans le token
+  // -> voir avec libsodium ?
+  // -> token plus gros avec HMAC
+  // -> regarder ce que font les autres implementations de QUIC
+  const std::array keyword = {'q', 'u', 'i', 'c', 'h', 'e'};
+  auto addrBytes = peer.toByteString();
+  PacketBuffer token;
+  token.reserve(keyword.size() + addrBytes.size() + dcid.size());
+  token.insert(token.end(), keyword.begin(), keyword.end());
+  token.insert(token.end(), addrBytes.begin(), addrBytes.end());
+  token.insert(token.end(), dcid.begin(), dcid.end());
+  return token;
+}
+
+// returns the original destination ID if the token is valid, nothing otherwise
+static std::optional<PacketBuffer> validateToken(const PacketBuffer& token, const PacketBuffer& dcid, const ComboAddress& peer)
+{
+  const std::array keyword = {'q', 'u', 'i', 'c', 'h', 'e'};
+  auto addrBytes = peer.toByteString();
+  auto minimumSize = keyword.size() + addrBytes.size();
+  if (token.size() <= minimumSize) {
+    return std::nullopt;
+  }
+  if (std::memcmp(&*keyword.begin(), &*token.begin(), keyword.size()) != 0) {
+    return std::nullopt;
+  }
+  if (std::memcmp(&token.at(keyword.size()), &*addrBytes.begin(), addrBytes.size()) != 0) {
+    return std::nullopt;
+  }
+  return PacketBuffer(token.begin() + keyword.size() + addrBytes.size(), token.end());
+}
+
+static void handleStatelessRetry(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, uint32_t version)
+{
+  auto newServerConnID = getCID();
+  if (!newServerConnID) {
+    return;
+  }
+
+  auto token = mintToken(serverConnID, peer);
+
+  PacketBuffer out(MAX_DATAGRAM_SIZE);
+  auto written = quiche_retry(clientConnID.data(), clientConnID.size(),
+                              serverConnID.data(), serverConnID.size(),
+                              newServerConnID->data(), newServerConnID->size(),
+                              token.data(), token.size(),
+                              version,
+                              out.data(), out.size());
+
+  if (written < 0) {
+    DEBUGLOG("failed to create retry packet " << written);
+    return;
+  }
+
+  out.resize(written);
+  sock.sendTo(std::string(out.begin(), out.end()), peer);
+}
+
+static void handleVersionNegociation(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer)
+{
+  PacketBuffer out(MAX_DATAGRAM_SIZE);
+
+  auto written = quiche_negotiate_version(clientConnID.data(), clientConnID.size(),
+                                          serverConnID.data(), serverConnID.size(),
+                                          out.data(), out.size());
+
+  if (written < 0) {
+    DEBUGLOG("failed to create vneg packet " << written);
+    return;
+  }
+  sock.sendTo(reinterpret_cast<const char*>(out.data()), written, peer);
+}
+
+static std::optional<std::reference_wrapper<Connection>> getConnection(const PacketBuffer& id)
+{
+  auto it = s_connections.find(id);
+  if (it == s_connections.end()) {
+    return std::nullopt;
+  }
+  return it->second;
+}
+
+static std::optional<std::reference_wrapper<Connection>> createConnection(QuicheConfig& config, const PacketBuffer& serverSideID, const PacketBuffer& originalDestinationID, const PacketBuffer& token, const ComboAddress& local, const ComboAddress& peer)
+{
+  auto quicheConn = QuicheConnection(quiche_accept(serverSideID.data(), serverSideID.size(),
+                                                   originalDestinationID.data(), originalDestinationID.size(),
+                                                   (struct sockaddr*) &local,
+                                                   local.getSocklen(),
+                                                   (struct sockaddr*) &peer,
+                                                   peer.getSocklen(),
+                                                   config.get()), quiche_conn_free);
+  auto conn = Connection(peer, std::move(quicheConn));
+  auto pair = s_connections.emplace(serverSideID,  std::move(conn));
+  return pair.first->second;
+}
+
+static void flushEgress(Socket& sock, Connection& conn) {
+  std::array<uint8_t, MAX_DATAGRAM_SIZE> out;
+  quiche_send_info send_info;
+
+  while (true) {
+    auto written = quiche_conn_send(conn.d_conn.get(), out.data(), out.size(), &send_info);
+
+    if (written == QUICHE_ERR_DONE) {
+      return;
+    }
+
+    if (written < 0) {
+      return;
+    }
+
+    sock.sendTo(reinterpret_cast<const char*>(out.data()), written, conn.d_peer);
+  }
+
+  // FIXME: update timers
+  // -> on peut appeler une fonction quiche pour savoir quand prochain timeout
+  // -> pas ici ?
+  // -> fin de loop event quand est le prochain plus petit timeout a venir
+  // -> relancer le multiplexer pour au plus ce temps la
+}
+
+std::unique_ptr<CrossProtocolQuery> getDOQCrossProtocolQueryFromDQ(DNSQuestion& dq, bool isResponse)
+{
+  if (!dq.ids.doqu) {
+    throw std::runtime_error("Trying to create a DoQ cross protocol query without a valid DoQ unit");
+  }
+
+  auto du = std::move(dq.ids.doqu);
+  if (&dq.ids != &du->ids) {
+   du->ids = std::move(dq.ids);
+  }
+
+  du->ids.origID = dq.getHeader()->id;
+
+  if (!isResponse) {
+    if (du->query.data() != dq.getMutableData().data()) {
+      du->query = std::move(dq.getMutableData());
+    }
+  }
+  else {
+    if (du->response.data() != dq.getMutableData().data()) {
+      du->response = std::move(dq.getMutableData());
+    }
+  }
+
+  return std::make_unique<DOQCrossProtocolQuery>(std::move(du), isResponse);
+}
+
+/*
+   We are not in the main DoQ thread but in the DoQ 'client' thread.
+*/
+static void processDOQQuery(DOQUnitUniquePtr&& unit, bool inMainThread = false)
+{
+  const auto handleImmediateResponse = [inMainThread](DOQUnitUniquePtr&& du, const char* reason) {
+    DEBUGLOG("handleImmediateResponse() reason=" << reason);
+    if (inMainThread) {
+      auto conn = getConnection(du->serverConnID);
+      handleResponse(*du->dsc->df, *conn, du->streamID, du->response);
+      /* so the unique pointer is stored in the InternalState which itself is stored in the unique pointer itself. We likely need
+         a better design, but for now let's just reset the internal one since we know it is no longer needed. */
+      du->ids.doqu.reset();
+    }
+    else {
+      sendDOQUnitToTheMainThread(std::move(du), reason);
+    }
+  };
+
+  auto& ids = unit->ids;
+  ids.doqu = std::move(unit);
+  auto& du = ids.doqu;
+  uint16_t queryId = 0;
+  ComboAddress remote;
+
+  try {
+    {
+      // if there was no EDNS, we add it with a large buffer size
+      // so we can use UDP to talk to the backend.
+      auto dh = const_cast<struct dnsheader*>(reinterpret_cast<const struct dnsheader*>(du->query.data()));
+
+      if (!dh->arcount) {
+        if (generateOptRR(std::string(), du->query, 4096, 4096, 0, false)) {
+          dh = const_cast<struct dnsheader*>(reinterpret_cast<const struct dnsheader*>(du->query.data())); // may have reallocated
+          dh->arcount = htons(1);
+          du->ids.ednsAdded = true;
+        }
+      }
+      else {
+        // we leave existing EDNS in place
+      }
+    }
+
+    remote = du->ids.origRemote;
+    DOQServerConfig* dsc = du->dsc;
+    auto& holders = dsc->holders;
+    ClientState& cs = *dsc->cs;
+
+    if (du->query.size() < sizeof(dnsheader)) {
+      // ++dnsdist::metrics::g_stats.nonCompliantQueries;
+      // ++cs.nonCompliantQueries;
+      handleImmediateResponse(std::move(du), "DoQ non-compliant query");
+      return;
+    }
+
+    // ++cs.queries;
+    // ++dnsdist::metrics::g_stats.queries;
+    du->ids.queryRealTime.start();
+
+    {
+      /* don't keep that pointer around, it will be invalidated if the buffer is ever resized */
+      struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(du->query.data());
+
+      if (!checkQueryHeaders(dh, cs)) {
+        // du->status_code = 400;
+        handleImmediateResponse(std::move(du), "DoQ invalid headers");
+        return;
+      }
+
+      if (dh->qdcount == 0) {
+        dh->rcode = RCode::NotImp;
+        dh->qr = true;
+        du->response = std::move(du->query);
+
+        handleImmediateResponse(std::move(du), "DoQ empty query");
+        return;
+      }
+
+      queryId = ntohs(dh->id);
+    }
+
+    auto downstream = du->downstream;
+    du->ids.qname = DNSName(reinterpret_cast<const char*>(du->query.data()), du->query.size(), sizeof(dnsheader), false, &du->ids.qtype, &du->ids.qclass);
+    DNSQuestion dq(du->ids, du->query);
+    const uint16_t* flags = getFlagsFromDNSHeader(dq.getHeader());
+    ids.origFlags = *flags;
+    du->ids.cs = &cs;
+
+    auto result = processQuery(dq, holders, downstream);
+    if (result == ProcessQueryResult::Drop) {
+      handleImmediateResponse(std::move(du), "DoQ dropped query");
+      return;
+    }
+    else if (result == ProcessQueryResult::Asynchronous) {
+      return;
+    }
+    else if (result == ProcessQueryResult::SendAnswer) {
+      if (du->response.empty()) {
+        du->response = std::move(du->query);
+      }
+      if (du->response.size() >= sizeof(dnsheader)) {
+        auto dh = reinterpret_cast<const struct dnsheader*>(du->response.data());
+
+        handleResponseSent(du->ids.qname, QType(du->ids.qtype), 0., du->ids.origDest, ComboAddress(), du->response.size(), *dh, dnsdist::Protocol::DoQ, dnsdist::Protocol::DoQ, false);
+      }
+      handleImmediateResponse(std::move(du), "DoQ self-answered response");
+      return;
+    }
+
+    if (result != ProcessQueryResult::PassToBackend) {
+      handleImmediateResponse(std::move(du), "DoQ no backend available");
+      return;
+    }
+
+    if (downstream == nullptr) {
+      handleImmediateResponse(std::move(du), "DoQ no backend available");
+      return;
+    }
+
+    du->downstream = downstream;
+
+    std::string proxyProtocolPayload;
+    /* we need to do this _before_ creating the cross protocol query because
+       after that the buffer will have been moved */
+    if (downstream->d_config.useProxyProtocol) {
+      proxyProtocolPayload = getProxyProtocolPayload(dq);
+    }
+
+    du->ids.origID = htons(queryId);
+    du->tcp = true;
+
+    /* this moves du->ids, careful! */
+    auto cpq = std::make_unique<DOQCrossProtocolQuery>(std::move(du), false);
+    cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
+
+    if (downstream->passCrossProtocolQuery(std::move(cpq))) {
+      return;
+    }
+    else {
+      if (inMainThread) {
+        du = cpq->releaseDU();
+        handleImmediateResponse(std::move(du), "DoQ internal error");
+      }
+      else {
+        cpq->handleInternalError();
+      }
+      return;
+    }
+  }
+  catch (const std::exception& e) {
+    vinfolog("Got an error in DOQ question thread while parsing a query from %s, id %d: %s", remote.toStringWithPort(), queryId, e.what());
+    handleImmediateResponse(std::move(du), "DoQ internal error");
+    return;
+  }
+
+  return;
+}
+
+static void flushResponses(pdns::channel::Receiver<DOQUnit>&& receiver)
+{
+  setThreadName("dnsdist/doq-responder");
+
+  for(;;) {
+    try {
+      auto tmp = receiver.receive();
+      if (!tmp) {
+        return ;
+      }
+
+      auto du = std::move(*tmp);
+      auto conn = getConnection(du->serverConnID);
+
+      handleResponse(*du->dsc->df, *conn, du->streamID, du->response);
+      
+    }
+    catch (const std::exception& e) {
+      errlog("Error while processing response received over DoQ: %s", e.what());
+    }
+    catch (...) {
+      errlog("Unspecified error while processing response received over DoQ");
+    }
+  }
+}
+
+static void dnsdistclient(pdns::channel::Receiver<DOQUnit>&& receiver)
+{
+  setThreadName("dnsdist/doq-cli");
+
+  for(;;) {
+    try {
+      auto tmp = receiver.receive();
+      if (!tmp) {
+        continue;
+      }
+      auto du = std::move(*tmp);
+      processDOQQuery(std::move(du), false);
+    }
+    catch (const std::exception& e) {
+      errlog("Error while processing query received over DoQ: %s", e.what());
+    }
+    catch (...) {
+      errlog("Unspecified error while processing query received over DoQ");
+    }
+  }
+}
+
+static void doq_dispatch_query(DOQServerConfig& dsc, PacketBuffer&& query, const ComboAddress& local, const ComboAddress& remote, const PacketBuffer& serverConnID, const uint64_t streamID)
+{
+  try {
+    /* we only parse it there as a sanity check, we will parse it again later */
+    DNSPacketMangler mangler(reinterpret_cast<char*>(query.data()), query.size());
+    mangler.skipDomainName();
+    mangler.skipBytes(4);
+    // Should we ensure message id is 0 ?
+
+    auto du = std::make_unique<DOQUnit>(std::move(query));
+    du->dsc = &dsc;
+    du->ids.origDest = local;
+    du->ids.origRemote = remote;
+    du->ids.protocol = dnsdist::Protocol::DoQ;
+    du->responseSender = &dsc.d_responseSender;
+    du->serverConnID = serverConnID;
+    du->streamID = streamID;
+
+    try {
+      if (!dsc.d_querySender.send(std::move(du))) {
+        vinfolog("Unable to pass a DoQ query to the DoQ worker thread because the pipe is full");
+      }
+    }
+    catch (...) {
+      vinfolog("Unable to pass a DoQ query to the DoQ worker thread because we couldn't write to the pipe: %s", stringerror());
+    }
+  }
+  catch (const std::exception& e) {
+    vinfolog("Had error parsing DoQ DNS packet from %s: %s", remote.toStringWithPort(), e.what());
+  }
+}
+
+// this is the entrypoint from dnsdist.cc
+void doqThread(ClientState* cs)
+{
+  try {
+    std::shared_ptr<DOQFrontend>& frontend = cs->doqFrontend;
+
+    frontend->d_server_config->cs = cs;
+    frontend->d_server_config->df = cs->doqFrontend;
+
+    std::thread dnsdistThread(dnsdistclient, std::move(frontend->d_server_config->d_queryReceiver));
+    dnsdistThread.detach();
+    std::thread responderThread(flushResponses, std::move(frontend->d_server_config->d_responseReceiver));
+    responderThread.detach();
+    setThreadName("dnsdist/doq");
+
+    Socket sock(cs->udpFD);
+
+    PacketBuffer buffer(std::numeric_limits<unsigned short>::max());
+
+    while (true) {
+      std::string bufferStr;
+      ComboAddress client;
+      if (waitForData(sock.getHandle(), 1, 0) > 0) {
+        sock.recvFrom(bufferStr, client);
+
+        uint32_t version{0};
+        uint8_t type;
+        std::array<uint8_t, QUICHE_MAX_CONN_ID_LEN> scid;
+        size_t scid_len = scid.size();
+        std::array<uint8_t, QUICHE_MAX_CONN_ID_LEN> dcid;
+        size_t dcid_len = dcid.size();
+        std::array<uint8_t, QUICHE_MAX_CONN_ID_LEN> odcid;
+        size_t odcid_len = odcid.size();
+        std::array<uint8_t, TOKEN_LEN> token;
+        size_t token_len = token.size();
+
+        auto res = quiche_header_info(reinterpret_cast<const uint8_t*>(bufferStr.data()), bufferStr.size(), LOCAL_CONN_ID_LEN,
+                                      &version, &type,
+                                      scid.data(), &scid_len,
+                                      dcid.data(), &dcid_len,
+                                      token.data(), &token_len);
+        if (res != 0) {
+          continue;
+        }
+
+        // destination connection ID, will have to be sent as original destination connection ID
+        PacketBuffer serverConnID(dcid.begin(), dcid.begin() + dcid_len);
+        // source connection ID, will have to be sent as destination connection ID
+        PacketBuffer clientConnID(scid.begin(), scid.begin() + scid_len);
+        auto conn = getConnection(serverConnID);
+
+        if (!conn) {
+          DEBUGLOG("Connection not found");
+          if (!quiche_version_is_supported(version)) {
+            DEBUGLOG("Unsupported version");
+            handleVersionNegociation(sock, clientConnID, serverConnID, client);
+            continue;
+          }
+
+          if (token_len == 0) {
+            /* stateless retry */
+            DEBUGLOG("No token received");
+            handleStatelessRetry(sock, clientConnID, serverConnID, client, version);
+            continue;
+          }
+
+          PacketBuffer tokenBuf(token.begin(), token.begin() + token_len);
+          auto originalDestinationID = validateToken(tokenBuf, serverConnID, client);
+          if (!originalDestinationID) {
+            DEBUGLOG("Discarding invalid token");
+            continue;
+          }
+
+          DEBUGLOG("Creating a new connection");
+          conn = createConnection(frontend->d_server_config->config, serverConnID, *originalDestinationID, tokenBuf, cs->local, client);
+          if (!conn) {
+            continue;
+          }
+        }
+        quiche_recv_info recv_info = {
+          (struct sockaddr*)&client,
+          client.getSocklen(),
+
+          (struct sockaddr*)&cs->local,
+          cs->local.getSocklen(),
+        };
+
+        auto done = quiche_conn_recv(conn->get().d_conn.get(), reinterpret_cast<uint8_t*>(bufferStr.data()), bufferStr.size(), &recv_info);
+        if (done < 0) {
+          continue;
+        }
+
+        if (quiche_conn_is_established(conn->get().d_conn.get())) {
+          auto readable = std::unique_ptr<quiche_stream_iter, decltype(&quiche_stream_iter_free)>(quiche_conn_readable(conn->get().d_conn.get()), quiche_stream_iter_free);
+
+          uint64_t streamID = 0;
+          while (quiche_stream_iter_next(readable.get(), &streamID)) {
+            bool fin = false;
+            buffer.resize(std::numeric_limits<unsigned short>::max());
+            auto received = quiche_conn_stream_recv(conn->get().d_conn.get(), streamID,
+                                                    buffer.data(), buffer.size(),
+                                                    &fin);
+            if (received < 2) {
+              break;
+            }
+            buffer.resize(received);
+
+            if (fin) {
+              buffer.erase(buffer.begin(), buffer.begin() + 2);
+              if (buffer.size() >= sizeof(dnsheader)) {
+                doq_dispatch_query(*(frontend->d_server_config), std::move(buffer), cs->local, client, serverConnID, streamID);
+              }
+            }
+          }
+        }
+        else {
+          DEBUGLOG("Connection not established");
+        }
+        /* FIXME: we should handle closed connections, timeouts */
+        // pacing QUIC ?
+        // quiche_send_info.at Queue avec les paquets a envoyer par date.
+      }
+      for (auto& conn : s_connections) {
+        flushEgress(sock, conn.second);
+      }
+    }
+
+  }
+  catch (const std::exception& e) {
+    DEBUGLOG("Caught fatal error: " << e.what());
+  }
+}
diff --git a/pdns/dnsdistdist/doq.hh b/pdns/dnsdistdist/doq.hh
new file mode 100644 (file)
index 0000000..a145f83
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <memory>
+#include "channel.hh"
+#include "iputils.hh"
+#include "libssl.hh"
+#include "noinitvector.hh"
+#include "stat_t.hh"
+#include "dnsdist-idstate.hh"
+
+#ifdef HAVE_DNS_OVER_QUIC
+
+#include <quiche.h>
+
+using QuicheConnection = std::unique_ptr<quiche_conn, decltype(&quiche_conn_free)>;
+using QuicheConfig = std::unique_ptr<quiche_config, decltype(&quiche_config_free)>;
+
+class Connection
+{
+public:
+  Connection(const ComboAddress& peer, std::unique_ptr<quiche_conn, decltype(&quiche_conn_free)>&& conn) :
+    d_peer(peer), d_conn(std::move(conn))
+    {
+    }
+  Connection(const Connection&) = delete;
+  Connection(Connection&&) = default;
+  Connection& operator=(const Connection&) = delete;
+  Connection& operator=(Connection&&) = default;
+  ~Connection() = default;
+
+  ComboAddress d_peer;
+  QuicheConnection d_conn;
+};
+
+#endif
+
+struct DOQServerConfig;
+struct DownstreamState;
+
+#ifdef HAVE_DNS_OVER_QUIC
+
+struct DOQFrontend
+{
+  DOQFrontend()
+    {
+    }
+
+  std::shared_ptr<DOQServerConfig> d_server_config{nullptr};
+  TLSConfig d_tlsConfig;
+  ComboAddress d_local;
+
+  void setup();
+#ifdef __linux__
+  // On Linux this gives us 128k pending queries (default is 8192 queries),
+  // which should be enough to deal with huge spikes
+  uint32_t d_internalPipeBufferSize{1024*1024};
+#else
+  uint32_t d_internalPipeBufferSize{0};
+#endif
+};
+
+struct DOQUnit
+{
+  DOQUnit(PacketBuffer&& q): query(std::move(q))
+  {
+    ids.ednsAdded = false;
+  }
+
+  DOQUnit(const DOQUnit&) = delete;
+  DOQUnit& operator=(const DOQUnit&) = delete;
+
+  InternalQueryState ids;
+  PacketBuffer query;
+  PacketBuffer response;
+  std::shared_ptr<DownstreamState> downstream{nullptr};
+  DOQServerConfig* dsc{nullptr};
+  pdns::channel::Sender<DOQUnit>* responseSender{nullptr};
+  size_t query_at{0};
+  size_t proxyProtocolPayloadSize{0};
+  int rsock{-1};
+  uint64_t streamID{0};
+  PacketBuffer serverConnID;
+  /* whether the query was re-sent to the backend over
+     TCP after receiving a truncated answer over UDP */
+  bool tcp{false};
+  bool truncated{false};
+};
+
+using DOQUnitUniquePtr = std::unique_ptr<DOQUnit>;
+
+struct CrossProtocolQuery;
+struct DNSQuestion;
+std::unique_ptr<CrossProtocolQuery> getDOQCrossProtocolQueryFromDQ(DNSQuestion& dq, bool isResponse);
+
+#else
+struct DOQUnit
+{
+};
+
+struct DOQFrontend
+{
+  DOQFrontend()
+    {
+    }
+  void setup()
+    {
+
+    }
+};
+
+#endif