]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Add unit tests for outgoing DoH
authorRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 31 Aug 2021 15:16:09 +0000 (17:16 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 13 Sep 2021 13:34:32 +0000 (15:34 +0200)
13 files changed:
pdns/dnsdist-lua.cc
pdns/dnsdist-tcp.cc
pdns/dnsdist.hh
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/dnsdist-healthchecks.cc
pdns/dnsdistdist/dnsdist-nghttp2.cc
pdns/dnsdistdist/dnsdist-nghttp2.hh
pdns/dnsdistdist/dnsdist-tcp-downstream.cc
pdns/dnsdistdist/dnsdist-tcp.hh
pdns/dnsdistdist/test-dnsdistnghttp2_cc.cc [new file with mode: 0644]
pdns/dnsdistdist/test-dnsdisttcp_cc.cc
regression-tests.dnsdist/dnsdisttests.py
regression-tests.dnsdist/test_OutgoingDOH.py

index 3042c8b59732da923ec04ab3d4dcdf89e127aae5..922e5e57861caffc8d77b81ffe0f429ce237bdab 100644 (file)
@@ -389,7 +389,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       }
 
       if(vars.count("retries")) {
-        ret->retries=std::stoi(boost::get<string>(vars["retries"]));
+        ret->d_retries = std::stoi(boost::get<string>(vars["retries"]));
       }
 
       if(vars.count("checkInterval")) {
index 71dac2f47c7953ee2b1d134827bad25f9ce5e433..f839b321f222202b0b3c7e50ebbf342148ea91de 100644 (file)
@@ -695,6 +695,8 @@ static void handleQuery(std::shared_ptr<IncomingTCPConnectionState>& state, cons
   ++state->d_currentQueriesCount;
 
   if (ds->isDoH()) {
+    vinfolog("Got query for %s|%s from %s (%s, %d bytes), relayed to %s", ids.qname.toLogString(), QType(ids.qtype).toString(), state->d_proxiedRemote.toStringWithPort(), (state->d_handler.isTLS() ? "DoT" : "TCP"), state->d_buffer.size(), ds->getName());
+
     auto incoming = std::make_shared<TCPCrossProtocolQuerySender>(state, state->d_threadData.crossProtocolResponsesPipe);
     auto cpq = std::make_unique<TCPCrossProtocolQuery>(std::move(state->d_buffer), std::move(ids), ds, incoming);
 
@@ -1175,14 +1177,19 @@ static void handleCrossProtocolResponse(int pipefd, FDMultiplexer::funcparam_t&
   delete tmp;
   tmp = nullptr;
 
-  if (response.d_response.d_buffer.empty()) {
-    response.d_state->notifyIOError(std::move(response.d_response.d_idstate), response.d_now);
-  }
-  else if (response.d_response.d_idstate.qtype == QType::AXFR || response.d_response.d_idstate.qtype == QType::IXFR) {
-    response.d_state->handleXFRResponse(response.d_now, std::move(response.d_response));
+  try {
+    if (response.d_response.d_buffer.empty()) {
+      response.d_state->notifyIOError(std::move(response.d_response.d_idstate), response.d_now);
+    }
+    else if (response.d_response.d_idstate.qtype == QType::AXFR || response.d_response.d_idstate.qtype == QType::IXFR) {
+      response.d_state->handleXFRResponse(response.d_now, std::move(response.d_response));
+    }
+    else {
+      response.d_state->handleResponse(response.d_now, std::move(response.d_response));
+    }
   }
-  else {
-    response.d_state->handleXFRResponse(response.d_now, std::move(response.d_response));
+  catch (...) {
+    /* no point bubbling up from there */
   }
 }
 
index aca96105309dadbf10da6d2ebd90039a3db7f214..31c62c7236cdd5adbb8aec29d68e8dfaed47646c 100644 (file)
@@ -730,7 +730,7 @@ struct DownstreamState
   unsigned int checkInterval{1};
   unsigned int lastCheck{0};
   const unsigned int sourceItf{0};
-  uint16_t retries{5};
+  uint16_t d_retries{5};
   uint16_t xpfRRCode{0};
   uint16_t checkTimeout{1000}; /* in milliseconds */
   uint8_t currentCheckFailures{0};
index 3e62c1ff184105c7e84793fb266aa0007021c356..4419c422fc958a7a13c667793af69b3e40857b04 100644 (file)
@@ -286,6 +286,7 @@ testrunner_SOURCES = \
        test-dnsdistdynblocks_hh.cc \
        test-dnsdistkvs_cc.cc \
        test-dnsdistlbpolicies_cc.cc \
+       test-dnsdistnghttp2_cc.cc \
        test-dnsdistpacketcache_cc.cc \
        test-dnsdistrings_cc.cc \
        test-dnsdistrules_cc.cc \
index 7ae87d9c19ba3110932a1ba3a10ae2a455aaaf15..f452932d65c6eb7070d98e2aee4c493a52f29c50 100644 (file)
@@ -431,7 +431,7 @@ bool queueHealthCheck(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared
     }
     return false;
   }
-  catch(...)
+  catch (...)
   {
     if (g_verboseHealthChecks) {
       infolog("Unknown exception while checking the health of backend %s", ds->getNameWithAddr());
@@ -451,8 +451,15 @@ void handleQueuedHealthChecks(FDMultiplexer& mplexer, bool initial)
       }
       break;
     }
+
+    handleH2Timeouts(mplexer, now);
+
     auto timeouts = mplexer.getTimeouts(now);
     for (const auto& timeout : timeouts) {
+      if (timeout.second.type() != typeid(std::shared_ptr<HealthCheckData>)) {
+        continue;
+      }
+
       auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(timeout.second);
       try {
         if (data->d_ioState) {
@@ -481,6 +488,9 @@ void handleQueuedHealthChecks(FDMultiplexer& mplexer, bool initial)
 
     timeouts = mplexer.getTimeouts(now, true);
     for (const auto& timeout : timeouts) {
+      if (timeout.second.type() != typeid(std::shared_ptr<HealthCheckData>)) {
+        continue;
+      }
       auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(timeout.second);
       try {
         data->d_ioState.reset();
index 2b503b95dff89de553eebc43df9e57b908630d96..f99d6870d355478a4a9cf953dfcbb6357f2995e9 100644 (file)
@@ -84,6 +84,7 @@ private:
     std::shared_ptr<TCPQuerySender> d_sender{nullptr};
     TCPQuery d_query;
     PacketBuffer d_buffer;
+    size_t d_queryPos{0};
     uint16_t d_responseCode{0};
     bool d_finished{false};
   };
@@ -92,6 +93,7 @@ private:
   void stopIO();
   void handleResponse(PendingRequest&& request);
   void handleResponseError(PendingRequest&& request, const struct timeval& now);
+  void handleIOError();
   uint32_t getConcurrentStreamsCount() const;
 
   size_t getUsageCount() const
@@ -106,7 +108,6 @@ private:
   std::unordered_map<int32_t, PendingRequest> d_currentStreams;
   PacketBuffer d_out;
   PacketBuffer d_in;
-  size_t d_queryPos{0};
   size_t d_outPos{0};
   size_t d_inPos{0};
   uint32_t d_highestStreamID{0};
@@ -116,8 +117,9 @@ private:
 class DownstreamDoHConnectionsManager
 {
 public:
-  static std::shared_ptr<DoHConnectionToBackend> getConnectionToDownstream(std::unique_ptr<FDMultiplexer>& mplexer, std::shared_ptr<DownstreamState>& ds, const struct timeval& now);
+  static std::shared_ptr<DoHConnectionToBackend> getConnectionToDownstream(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& ds, const struct timeval& now);
   static void releaseDownstreamConnection(std::shared_ptr<DoHConnectionToBackend>&& conn);
+  static bool removeDownstreamConnection(std::shared_ptr<DoHConnectionToBackend>& conn);
   static void cleanupClosedConnections(struct timeval now);
   static size_t clear();
 
@@ -147,21 +149,42 @@ void DoHConnectionToBackend::handleResponse(PendingRequest&& request)
 {
   struct timeval now;
   gettimeofday(&now, nullptr);
-  request.d_sender->handleResponse(now, TCPResponse(std::move(request.d_buffer), std::move(request.d_query.d_idstate), shared_from_this()));
+  try {
+    request.d_sender->handleResponse(now, TCPResponse(std::move(request.d_buffer), std::move(request.d_query.d_idstate), shared_from_this()));
+  }
+  catch (const std::exception& e) {
+    vinfolog("Got exception while handling response for cross-protocol DoH: %s", e.what());
+  }
 }
 
 void DoHConnectionToBackend::handleResponseError(PendingRequest&& request, const struct timeval& now)
 {
-  request.d_sender->notifyIOError(std::move(request.d_query.d_idstate), now);
+  try {
+    request.d_sender->notifyIOError(std::move(request.d_query.d_idstate), now);
+  }
+  catch (const std::exception& e) {
+    vinfolog("Got exception while handling response for cross-protocol DoH: %s", e.what());
+  }
 }
 
-void DoHConnectionToBackend::handleTimeout(const struct timeval& now, bool write)
+void DoHConnectionToBackend::handleIOError()
 {
   d_connectionDied = true;
+  nghttp2_session_terminate_session(d_session.get(), NGHTTP2_PROTOCOL_ERROR);
+
+  struct timeval now;
+  gettimeofday(&now, nullptr);
   for (auto& request : d_currentStreams) {
     handleResponseError(std::move(request.second), now);
   }
+
   d_currentStreams.clear();
+  stopIO();
+}
+
+void DoHConnectionToBackend::handleTimeout(const struct timeval& now, bool write)
+{
+  handleIOError();
 }
 
 bool DoHConnectionToBackend::canBeReused() const
@@ -182,18 +205,6 @@ bool DoHConnectionToBackend::canBeReused() const
   return true;
 }
 
-#define MAKE_NV(NAME, VALUE, VALUELEN)                           \
-  {                                                              \
-    (uint8_t*)NAME, (uint8_t*)VALUE, sizeof(NAME) - 1, VALUELEN, \
-      NGHTTP2_NV_FLAG_NONE                                       \
-  }
-
-#define MAKE_NV2(NAME, VALUE)                                             \
-  {                                                                       \
-    (uint8_t*)NAME, (uint8_t*)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \
-      NGHTTP2_NV_FLAG_NONE                                                \
-  }
-
 const std::unordered_map<std::string, std::string> DoHConnectionToBackend::s_constants = {
   {"method-name", ":method"},
   {"method-value", "POST"},
@@ -235,9 +246,8 @@ void DoHConnectionToBackend::addDynamicHeader(std::vector<nghttp2_nv>& headers,
 
 void DoHConnectionToBackend::queueQuery(std::shared_ptr<TCPQuerySender>& sender, TCPQuery&& query)
 {
+  // cerr<<"in "<<__PRETTY_FUNCTION__<<" with query ID "<<ntohs(dh->id)<<endl;
   auto payloadSize = std::to_string(query.d_buffer.size());
-  d_currentQuery = std::move(query);
-  d_queryPos = 0;
 
   bool addXForwarded = d_ds->d_addXForwardedHeaders;
 
@@ -259,24 +269,24 @@ void DoHConnectionToBackend::queueQuery(std::shared_ptr<TCPQuerySender>& sender,
   addStaticHeader(headers, "user-agent-name", "user-agent-value");
   addDynamicHeader(headers, "content-length-name", payloadSize);
   /* no need to add these headers for health-check queries */
-  if (addXForwarded && d_currentQuery.d_idstate.origRemote.getPort() != 0) {
-    remote = d_currentQuery.d_idstate.origRemote.toString();
-    remotePort = std::to_string(d_currentQuery.d_idstate.origRemote.getPort());
+  if (addXForwarded && query.d_idstate.origRemote.getPort() != 0) {
+    remote = query.d_idstate.origRemote.toString();
+    remotePort = std::to_string(query.d_idstate.origRemote.getPort());
     addDynamicHeader(headers, "x-forwarded-for-name", remote);
     addDynamicHeader(headers, "x-forwarded-port-name", remotePort);
-    if (d_currentQuery.d_idstate.cs != nullptr) {
-      if (d_currentQuery.d_idstate.cs->isUDP()) {
+    if (query.d_idstate.cs != nullptr) {
+      if (query.d_idstate.cs->isUDP()) {
         addStaticHeader(headers, "x-forwarded-proto-name", "x-forwarded-proto-value-dns-over-udp");
       }
-      else if (d_currentQuery.d_idstate.cs->isDoH()) {
-        if (d_currentQuery.d_idstate.cs->hasTLS()) {
+      else if (query.d_idstate.cs->isDoH()) {
+        if (query.d_idstate.cs->hasTLS()) {
           addStaticHeader(headers, "x-forwarded-proto-name", "x-forwarded-proto-value-dns-over-https");
         }
         else {
           addStaticHeader(headers, "x-forwarded-proto-name", "x-forwarded-proto-value-dns-over-http");
         }
       }
-      else if (d_currentQuery.d_idstate.cs->hasTLS()) {
+      else if (query.d_idstate.cs->hasTLS()) {
         addStaticHeader(headers, "x-forwarded-proto-name", "x-forwarded-proto-value-dns-over-tls");
       }
       else {
@@ -285,50 +295,58 @@ void DoHConnectionToBackend::queueQuery(std::shared_ptr<TCPQuerySender>& sender,
     }
   }
 
+  PendingRequest pending;
+  pending.d_query = std::move(query);
+  pending.d_sender = std::move(sender);
+
+  uint32_t streamId = nghttp2_session_get_next_stream_id(d_session.get());
+  auto insertPair = d_currentStreams.insert({streamId, std::move(pending)});
+  if (!insertPair.second) {
+    /* there is a stream ID collision, something is very wrong! */
+    d_connectionDied = true;
+    nghttp2_session_terminate_session(d_session.get(), NGHTTP2_NO_ERROR);
+    throw std::runtime_error("Stream ID collision");
+  }
+
   /* if data_prd is not NULL, it provides data which will be sent in subsequent DATA frames. In this case, a method that allows request message bodies (https://tools.ietf.org/html/rfc7231#section-4) must be specified with :method key (e.g. POST). This function does not take ownership of the data_prd. The function copies the members of the data_prd. If data_prd is NULL, HEADERS have END_STREAM set.
    */
   nghttp2_data_provider data_provider;
+
+  /* we will not use this pointer */
   data_provider.source.ptr = this;
   data_provider.read_callback = [](nghttp2_session* session, int32_t stream_id, uint8_t* buf, size_t length, uint32_t* data_flags, nghttp2_data_source* source, void* user_data) -> ssize_t {
-    auto userData = reinterpret_cast<DoHConnectionToBackend*>(user_data);
+    auto conn = reinterpret_cast<DoHConnectionToBackend*>(user_data);
+    auto& request = conn->d_currentStreams.at(stream_id);
     size_t toCopy = 0;
-    if (userData->d_queryPos < userData->d_currentQuery.d_buffer.size()) {
-      size_t remaining = userData->d_currentQuery.d_buffer.size() - userData->d_queryPos;
+    if (request.d_queryPos < request.d_query.d_buffer.size()) {
+      size_t remaining = request.d_query.d_buffer.size() - request.d_queryPos;
       toCopy = length > remaining ? remaining : length;
-      memcpy(buf, &userData->d_currentQuery.d_buffer.at(userData->d_queryPos), toCopy);
-      userData->d_queryPos += toCopy;
+      memcpy(buf, &request.d_query.d_buffer.at(request.d_queryPos), toCopy);
+      request.d_queryPos += toCopy;
     }
 
-    if (userData->d_queryPos >= userData->d_currentQuery.d_buffer.size()) {
+    if (request.d_queryPos >= request.d_query.d_buffer.size()) {
       *data_flags |= NGHTTP2_DATA_FLAG_EOF;
     }
     return toCopy;
   };
 
-  auto stream_id = nghttp2_submit_request(d_session.get(), nullptr, headers.data(), headers.size(), &data_provider, this);
-  if (stream_id < 0) {
+  auto newStreamId = nghttp2_submit_request(d_session.get(), nullptr, headers.data(), headers.size(), &data_provider, this);
+  if (newStreamId < 0) {
     d_connectionDied = true;
-    throw std::runtime_error("Error submitting HTTP request:" + std::string(nghttp2_strerror(stream_id)));
+    d_currentStreams.erase(streamId);
+    throw std::runtime_error("Error submitting HTTP request:" + std::string(nghttp2_strerror(newStreamId)));
   }
-  //cerr<<"stream ID is "<<stream_id<<" for a query of size "<<payloadSize<<endl;
+  // cerr<<"stream ID is "<<newStreamId<<" for a query of size "<<payloadSize<<endl;
 
   auto rv = nghttp2_session_send(d_session.get());
   if (rv != 0) {
     d_connectionDied = true;
+    d_currentStreams.erase(streamId);
     throw std::runtime_error("Error in nghttp2_session_send:" + std::to_string(rv));
   }
-  PendingRequest request;
-  request.d_query = std::move(d_currentQuery);
-  request.d_sender = std::move(sender);
-  auto insertPair = d_currentStreams.insert({stream_id, std::move(request)});
-  if (!insertPair.second) {
-    /* there is a stream ID collision, something is very wrong! */
-    d_connectionDied = true;
-    nghttp2_session_terminate_session(d_session.get(), NGHTTP2_NO_ERROR);
-    throw std::runtime_error("Stream ID collision");
-  }
 
-  d_highestStreamID = stream_id;
+  d_highestStreamID = newStreamId;
 }
 
 class DoHClientThreadData
@@ -357,21 +375,26 @@ void DoHConnectionToBackend::handleReadableIOCallback(int fd, FDMultiplexer::fun
   do {
     conn->d_inPos = 0;
     conn->d_in.resize(conn->d_in.size() + 512);
-    //cerr<<"trying to read "<<conn->d_in.size()<<endl;
+    // cerr<<"trying to read "<<conn->d_in.size()<<endl;
     try {
       IOState newState = conn->d_handler->tryRead(conn->d_in, conn->d_inPos, conn->d_in.size(), true);
-      //cerr<<"got a "<<(int)newState<<" state and "<<conn->d_inPos<<" bytes"<<endl;
+      // cerr<<"got a "<<(int)newState<<" state and "<<conn->d_inPos<<" bytes"<<endl;
       conn->d_in.resize(conn->d_inPos);
-      if (newState == IOState::Done) {
+
+      if (conn->d_inPos > 0) {
+        /* we got something */
         auto readlen = nghttp2_session_mem_recv(conn->d_session.get(), conn->d_in.data(), conn->d_inPos);
-        //cerr<<"nghttp2_session_mem_recv returned "<<readlen<<endl;
+        // cerr<<"nghttp2_session_mem_recv returned "<<readlen<<endl;
         /* as long as we don't require a pause by returning nghttp2_error.NGHTTP2_ERR_PAUSE from a CB,
            all data should be consumed before returning */
         if (readlen > 0 && static_cast<size_t>(readlen) < conn->d_inPos) {
-          cerr << "Fatal error: " << nghttp2_strerror((int)readlen) << endl;
-          return;
+          throw std::runtime_error("Fatal error while passing received data to nghttp2: " + std::string(nghttp2_strerror((int)readlen)));
         }
+        // cerr<<"after read send"<<endl;
         nghttp2_session_send(conn->d_session.get());
+      }
+
+      if (newState == IOState::Done) {
         if (conn->getConcurrentStreamsCount() == 0) {
           conn->stopIO();
           ioGuard.release();
@@ -380,6 +403,7 @@ void DoHConnectionToBackend::handleReadableIOCallback(int fd, FDMultiplexer::fun
       }
       else {
         if (newState == IOState::NeedWrite) {
+          // cerr<<"need write"<<endl;
           conn->updateIO(IOState::NeedWrite, handleReadableIOCallback);
         }
         ioGuard.release();
@@ -387,7 +411,8 @@ void DoHConnectionToBackend::handleReadableIOCallback(int fd, FDMultiplexer::fun
       }
     }
     catch (const std::exception& e) {
-      cerr << "Exception while trying to read from HTTP backend connection: " << e.what() << endl;
+      vinfolog("Exception while trying to read from HTTP backend connection: %s", e.what());
+      conn->handleIOError();
       break;
     }
   } while (conn->getConcurrentStreamsCount() > 0);
@@ -401,30 +426,41 @@ void DoHConnectionToBackend::handleWritableIOCallback(int fd, FDMultiplexer::fun
   }
   IOStateGuard ioGuard(conn->d_ioState);
 
-  //cerr<<"trying to write "<<conn->d_out.size()-conn->d_outPos<<endl;
+  // cerr<<"in "<<__PRETTY_FUNCTION__<<" trying to write "<<conn->d_out.size()-conn->d_outPos<<endl;
   try {
     IOState newState = conn->d_handler->tryWrite(conn->d_out, conn->d_outPos, conn->d_out.size());
-    //cerr<<"got a "<<(int)newState<<" state, "<<conn->d_out.size()-conn->d_outPos<<" bytes remaining"<<endl;
+    // cerr<<"got a "<<(int)newState<<" state, "<<conn->d_out.size()-conn->d_outPos<<" bytes remaining"<<endl;
     if (newState == IOState::NeedRead) {
       conn->updateIO(IOState::NeedRead, handleWritableIOCallback);
     }
     else if (newState == IOState::Done) {
+      // cerr<<"done, buffer size was "<<conn->d_out.size()<<", pos was "<<conn->d_outPos<<endl;
       ++conn->d_queries;
       conn->d_out.clear();
       conn->d_outPos = 0;
       conn->stopIO();
-      conn->updateIO(IOState::NeedRead, handleReadableIOCallback);
+      if (conn->getConcurrentStreamsCount() > 0) {
+        conn->updateIO(IOState::NeedRead, handleReadableIOCallback);
+      }
     }
     ioGuard.release();
   }
   catch (const std::exception& e) {
-    cerr << "Exception while trying to write (ready) to HTTP backend connection: " << e.what() << endl;
+    vinfolog("Exception while trying to write (ready) to HTTP backend connection: %s", e.what());
+    conn->handleIOError();
   }
 }
 
 void DoHConnectionToBackend::stopIO()
 {
   d_ioState->reset();
+
+  if (d_connectionDied) {
+    /* remove ourselves from the connection cache, this might mean that our
+       reference count drops to zero after that, so we need to be careful */
+    auto shared = std::dynamic_pointer_cast<DoHConnectionToBackend>(shared_from_this());
+    DownstreamDoHConnectionsManager::removeDownstreamConnection(shared);
+  }
 }
 
 void DoHConnectionToBackend::updateIO(IOState newState, FDMultiplexer::callbackfunc_t callback)
@@ -492,19 +528,25 @@ ssize_t DoHConnectionToBackend::send_callback(nghttp2_session* session, const ui
 
   if (bufferWasEmpty) {
     try {
+      // cerr<<"in "<<__PRETTY_FUNCTION__<<" trying to write "<<conn->d_out.size()-conn->d_outPos<<endl;
       auto state = conn->d_handler->tryWrite(conn->d_out, conn->d_outPos, conn->d_out.size());
+      // cerr<<"got a "<<(int)state<<" state, "<<conn->d_out.size()-conn->d_outPos<<" bytes remaining"<<endl;
       if (state == IOState::Done) {
         ++conn->d_queries;
         conn->d_out.clear();
         conn->d_outPos = 0;
-        conn->addToIOState(IOState::NeedRead, handleReadableIOCallback);
+        conn->stopIO();
+        if (conn->getConcurrentStreamsCount() > 0) {
+          conn->updateIO(IOState::NeedRead, handleReadableIOCallback);
+        }
       }
       else {
         conn->updateIO(state, handleWritableIOCallback);
       }
     }
     catch (const std::exception& e) {
-      cerr << "Exception while trying to write (send) to HTTP backend connection: " << e.what() << endl;
+      vinfolog("Exception while trying to write (send) to HTTP backend connection: %s", e.what());
+      conn->handleIOError();
     }
   }
 
@@ -514,7 +556,7 @@ ssize_t DoHConnectionToBackend::send_callback(nghttp2_session* session, const ui
 int DoHConnectionToBackend::on_frame_recv_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data)
 {
   DoHConnectionToBackend* conn = reinterpret_cast<DoHConnectionToBackend*>(user_data);
-  //cerr<<"Frame type is "<<std::to_string(frame->hd.type)<<endl;
+  // cerr<<"Frame type is "<<std::to_string(frame->hd.type)<<endl;
 #if 0
   switch (frame->hd.type) {
   case NGHTTP2_HEADERS:
@@ -543,7 +585,7 @@ int DoHConnectionToBackend::on_frame_recv_callback(nghttp2_session* session, con
   if ((frame->hd.type == NGHTTP2_HEADERS || frame->hd.type == NGHTTP2_DATA) && frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
     auto stream = conn->d_currentStreams.find(frame->hd.stream_id);
     if (stream != conn->d_currentStreams.end()) {
-      //cerr<<"Stream "<<frame->hd.stream_id<<" is now finished"<<endl;
+      // cerr<<"Stream "<<frame->hd.stream_id<<" is now finished"<<endl;
       stream->second.d_finished = true;
 
       auto request = std::move(stream->second);
@@ -575,7 +617,7 @@ int DoHConnectionToBackend::on_frame_recv_callback(nghttp2_session* session, con
 int DoHConnectionToBackend::on_data_chunk_recv_callback(nghttp2_session* session, uint8_t flags, int32_t stream_id, const uint8_t* data, size_t len, void* user_data)
 {
   DoHConnectionToBackend* conn = reinterpret_cast<DoHConnectionToBackend*>(user_data);
-  //cerr<<"Got data of size "<<len<<" for stream "<<stream_id<<endl;
+  // cerr<<"Got data of size "<<len<<" for stream "<<stream_id<<endl;
   auto stream = conn->d_currentStreams.find(stream_id);
   if (stream == conn->d_currentStreams.end()) {
     vinfolog("Unable to match the stream ID %d to a known one!", stream_id);
@@ -624,13 +666,12 @@ int DoHConnectionToBackend::on_stream_close_callback(nghttp2_session* session, i
     return 0;
   }
 
-  cerr << "Stream " << stream_id << " closed with error_code=" << error_code << endl;
+  // cerr << "Stream " << stream_id << " closed with error_code=" << error_code << endl;
   conn->d_connectionDied = true;
 
   auto stream = conn->d_currentStreams.find(stream_id);
   if (stream == conn->d_currentStreams.end()) {
     /* we don't care, then */
-    cerr << "we don't care" << endl;
     return 0;
   }
 
@@ -639,9 +680,16 @@ int DoHConnectionToBackend::on_stream_close_callback(nghttp2_session* session, i
   auto request = std::move(stream->second);
   conn->d_currentStreams.erase(stream->first);
 
-  //cerr<<"in "<<__PRETTY_FUNCTION__<<", looking for a connection to send a query of size "<<request.d_query.d_buffer.size()<<endl;
-  auto downstream = DownstreamDoHConnectionsManager::getConnectionToDownstream(conn->d_mplexer, conn->d_ds, now);
-  downstream->queueQuery(request.d_sender, std::move(request.d_query));
+  // cerr<<"Query has "<<request.d_query.d_downstreamFailures<<" failures, backend limit is "<<conn->d_ds->d_retries<<endl;
+  if (request.d_query.d_downstreamFailures < conn->d_ds->d_retries) {
+    // cerr<<"in "<<__PRETTY_FUNCTION__<<", looking for a connection to send a query of size "<<request.d_query.d_buffer.size()<<endl;
+    ++request.d_query.d_downstreamFailures;
+    auto downstream = DownstreamDoHConnectionsManager::getConnectionToDownstream(conn->d_mplexer, conn->d_ds, now);
+    downstream->queueQuery(request.d_sender, std::move(request.d_query));
+  }
+  else {
+    conn->handleResponseError(std::move(request), now);
+  }
 
   //cerr<<"we now have "<<conn->getConcurrentStreamsCount()<<" concurrent connections"<<endl;
   if (conn->getConcurrentStreamsCount() == 0) {
@@ -755,6 +803,35 @@ size_t DownstreamDoHConnectionsManager::s_maxCachedConnectionsPerDownstream{10};
 time_t DownstreamDoHConnectionsManager::s_nextCleanup{0};
 uint16_t DownstreamDoHConnectionsManager::s_cleanupInterval{60};
 
+size_t DownstreamDoHConnectionsManager::clear()
+{
+  size_t result = 0;
+  for (const auto& backend : t_downstreamConnections) {
+    result += backend.second.size();
+  }
+  t_downstreamConnections.clear();
+  return result;
+}
+
+bool DownstreamDoHConnectionsManager::removeDownstreamConnection(std::shared_ptr<DoHConnectionToBackend>& conn)
+{
+  bool found = false;
+  auto backendIt = t_downstreamConnections.find(conn->getDS()->getID());
+  if (backendIt == t_downstreamConnections.end()) {
+    return found;
+  }
+
+  for (auto connIt = backendIt->second.begin(); connIt != backendIt->second.end(); ++connIt) {
+    if (*connIt == conn) {
+      backendIt->second.erase(connIt);
+      found = true;
+      break;
+    }
+  }
+
+  return found;
+}
+
 void DownstreamDoHConnectionsManager::cleanupClosedConnections(struct timeval now)
 {
   struct timeval freshCutOff = now;
@@ -790,7 +867,7 @@ void DownstreamDoHConnectionsManager::cleanupClosedConnections(struct timeval no
   }
 }
 
-std::shared_ptr<DoHConnectionToBackend> DownstreamDoHConnectionsManager::getConnectionToDownstream(std::unique_ptr<FDMultiplexer>& mplexer, std::shared_ptr<DownstreamState>& ds, const struct timeval& now)
+std::shared_ptr<DoHConnectionToBackend> DownstreamDoHConnectionsManager::getConnectionToDownstream(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& ds, const struct timeval& now)
 {
   std::shared_ptr<DoHConnectionToBackend> result;
   struct timeval freshCutOff = now;
@@ -902,23 +979,8 @@ static void dohClientThread(int crossProtocolPipeFD)
 
     if (now.tv_sec > lastTimeoutScan) {
       lastTimeoutScan = now.tv_sec;
-      auto expiredReadConns = data.mplexer->getTimeouts(now, false);
-      for (const auto& cbData : expiredReadConns) {
-        if (cbData.second.type() == typeid(std::shared_ptr<DoHConnectionToBackend>)) {
-          auto conn = boost::any_cast<std::shared_ptr<DoHConnectionToBackend>>(cbData.second);
-          vinfolog("Timeout (read) from remote DoH backend %s", conn->getBackendName());
-          conn->handleTimeout(now, false);
-        }
-      }
 
-      auto expiredWriteConns = data.mplexer->getTimeouts(now, true);
-      for (const auto& cbData : expiredWriteConns) {
-        if (cbData.second.type() == typeid(std::shared_ptr<DoHConnectionToBackend>)) {
-          auto conn = boost::any_cast<std::shared_ptr<DoHConnectionToBackend>>(cbData.second);
-          vinfolog("Timeout (write) from remote DoH backend %s", conn->getBackendName());
-          conn->handleTimeout(now, true);
-        }
-      }
+      handleH2Timeouts(*data.mplexer, now);
 
       if (g_dohStatesDumpRequested > 0) {
         /* just to keep things clean in the output, debug only */
@@ -1134,11 +1196,55 @@ bool sendH2Query(const std::shared_ptr<DownstreamState>& ds, std::unique_ptr<FDM
   struct timeval now;
   gettimeofday(&now, nullptr);
 
-  auto newConnection = std::make_shared<DoHConnectionToBackend>(ds, mplexer, now);
-  newConnection->setHealthCheck(healthCheck);
-  newConnection->queueQuery(sender, std::move(query));
+  if (healthCheck) {
+    /* always do health-checks over a new connection */
+    auto newConnection = std::make_shared<DoHConnectionToBackend>(ds, mplexer, now);
+    newConnection->setHealthCheck(healthCheck);
+    newConnection->queueQuery(sender, std::move(query));
+  }
+  else {
+    auto connection = DownstreamDoHConnectionsManager::getConnectionToDownstream(mplexer, ds, now);
+    connection->queueQuery(sender, std::move(query));
+  }
+
   return true;
 #else /* HAVE_NGHTTP2 */
   return false;
 #endif /* HAVE_NGHTTP2 */
 }
+
+size_t clearH2Connections()
+{
+  size_t cleared = 0;
+#ifdef HAVE_NGHTTP2
+  cleared = DownstreamDoHConnectionsManager::clear();
+#endif /* HAVE_NGHTTP2 */
+  return cleared;
+}
+
+size_t handleH2Timeouts(FDMultiplexer& mplexer, const struct timeval& now)
+{
+  size_t got = 0;
+#ifdef HAVE_NGHTTP2
+  auto expiredReadConns = mplexer.getTimeouts(now, false);
+  for (const auto& cbData : expiredReadConns) {
+    if (cbData.second.type() == typeid(std::shared_ptr<DoHConnectionToBackend>)) {
+      auto conn = boost::any_cast<std::shared_ptr<DoHConnectionToBackend>>(cbData.second);
+      vinfolog("Timeout (read) from remote DoH backend %s", conn->getBackendName());
+      conn->handleTimeout(now, false);
+      ++got;
+    }
+  }
+
+  auto expiredWriteConns = mplexer.getTimeouts(now, true);
+  for (const auto& cbData : expiredWriteConns) {
+    if (cbData.second.type() == typeid(std::shared_ptr<DoHConnectionToBackend>)) {
+      auto conn = boost::any_cast<std::shared_ptr<DoHConnectionToBackend>>(cbData.second);
+      vinfolog("Timeout (write) from remote DoH backend %s", conn->getBackendName());
+      conn->handleTimeout(now, true);
+      ++got;
+    }
+  }
+#endif /* HAVE_NGHTTP2 */
+  return got;
+}
index 5949cd3cdda7ef28e9c3490cb3846381aae38252..afc87375866723dc3138733e9f63ce1ee2f08c92 100644 (file)
@@ -69,3 +69,5 @@ bool setupDoHClientProtocolNegotiation(std::shared_ptr<TLSCtx>& ctx);
 /* opens a new HTTP/2 connection to the supplied backend (attached to the supplied multiplexer), sends the query,
    waits for the response to come back or an error to occur then notifies the sender, closing the connection. */
 bool sendH2Query(const std::shared_ptr<DownstreamState>& ds, std::unique_ptr<FDMultiplexer>& mplexer, std::shared_ptr<TCPQuerySender>& sender, InternalQuery&& query, bool healthCheck);
+size_t handleH2Timeouts(FDMultiplexer& mplexer, const struct timeval& now);
+size_t clearH2Connections();
index da37224087d990437189121e83288a76b8e9e2c4..5935b06448afa9bb9890383d4f459eee87d450d8 100644 (file)
@@ -190,9 +190,9 @@ void TCPConnectionToBackend::handleIO(std::shared_ptr<TCPConnectionToBackend>& c
 
     if (connectionDied) {
 
-      DEBUGLOG("connection died, number of failures is "<<conn->d_downstreamFailures<<", retries is "<<conn->d_ds->retries);
+      DEBUGLOG("connection died, number of failures is "<<conn->d_downstreamFailures<<", retries is "<<conn->d_ds->d_retries);
 
-      if (conn->d_downstreamFailures < conn->d_ds->retries) {
+      if (conn->d_downstreamFailures < conn->d_ds->d_retries) {
 
         conn->d_ioState.reset();
         ioGuard.release();
@@ -409,12 +409,12 @@ bool TCPConnectionToBackend::reconnect()
     catch (const std::runtime_error& e) {
       vinfolog("Connection to downstream server %s failed: %s", d_ds->getName(), e.what());
       d_downstreamFailures++;
-      if (d_downstreamFailures >= d_ds->retries) {
+      if (d_downstreamFailures >= d_ds->d_retries) {
         throw;
       }
     }
   }
-  while (d_downstreamFailures < d_ds->retries);
+  while (d_downstreamFailures < d_ds->d_retries);
 
   return false;
 }
index 312d0b72a46507ada04e3eada3e4b22ccc294f6d..2049cd3cdf7eb21228fe957cf4b52c424edf6d89 100644 (file)
@@ -80,7 +80,7 @@ struct InternalQuery
   }
 
   InternalQuery(InternalQuery&& rhs) :
-    d_idstate(std::move(rhs.d_idstate)), d_buffer(std::move(rhs.d_buffer)), d_proxyProtocolPayload(std::move(rhs.d_proxyProtocolPayload)), d_xfrMasterSerial(rhs.d_xfrMasterSerial), d_xfrSerialCount(rhs.d_xfrSerialCount), d_xfrMasterSerialCount(rhs.d_xfrMasterSerialCount), d_proxyProtocolPayloadAdded(rhs.d_proxyProtocolPayloadAdded)
+    d_idstate(std::move(rhs.d_idstate)), d_buffer(std::move(rhs.d_buffer)), d_proxyProtocolPayload(std::move(rhs.d_proxyProtocolPayload)), d_xfrMasterSerial(rhs.d_xfrMasterSerial), d_xfrSerialCount(rhs.d_xfrSerialCount), d_downstreamFailures(rhs.d_downstreamFailures), d_xfrMasterSerialCount(rhs.d_xfrMasterSerialCount), d_proxyProtocolPayloadAdded(rhs.d_proxyProtocolPayloadAdded)
   {
   }
   InternalQuery& operator=(InternalQuery&& rhs)
@@ -90,6 +90,7 @@ struct InternalQuery
     d_proxyProtocolPayload = std::move(rhs.d_proxyProtocolPayload);
     d_xfrMasterSerial = rhs.d_xfrMasterSerial;
     d_xfrSerialCount = rhs.d_xfrSerialCount;
+    d_downstreamFailures = rhs.d_downstreamFailures;
     d_xfrMasterSerialCount = rhs.d_xfrMasterSerialCount;
     d_proxyProtocolPayloadAdded = rhs.d_proxyProtocolPayloadAdded;
     return *this;
@@ -108,6 +109,7 @@ struct InternalQuery
   std::string d_proxyProtocolPayload;
   uint32_t d_xfrMasterSerial{0};
   uint32_t d_xfrSerialCount{0};
+  uint32_t d_downstreamFailures{0};
   uint8_t d_xfrMasterSerialCount{0};
   bool d_xfrStarted{false};
   bool d_proxyProtocolPayloadAdded{false};
diff --git a/pdns/dnsdistdist/test-dnsdistnghttp2_cc.cc b/pdns/dnsdistdist/test-dnsdistnghttp2_cc.cc
new file mode 100644 (file)
index 0000000..0723df5
--- /dev/null
@@ -0,0 +1,1753 @@
+/*
+ * 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.
+ */
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+
+#include "dnswriter.hh"
+#include "dnsdist.hh"
+#include "dnsdist-proxy-protocol.hh"
+#include "dnsdist-rings.hh"
+#include "dnsdist-nghttp2.hh"
+
+#ifdef HAVE_NGHTTP2
+#include <nghttp2/nghttp2.h>
+
+BOOST_AUTO_TEST_SUITE(test_dnsdistnghttp2_cc)
+
+struct ExpectedStep
+{
+public:
+  enum class ExpectedRequest { handshakeClient, readFromClient, writeToClient, closeClient, connectToBackend, readFromBackend, writeToBackend, closeBackend };
+
+  ExpectedStep(ExpectedRequest r, IOState n, size_t b = 0, std::function<void(int descriptor, const ExpectedStep& step)> fn = nullptr): cb(fn), request(r), nextState(n), bytes(b)
+  {
+  }
+
+  std::function<void(int descriptor, const ExpectedStep& step)> cb{nullptr};
+  ExpectedRequest request;
+  IOState nextState;
+  size_t bytes{0};
+};
+
+struct ExpectedData
+{
+  PacketBuffer d_query;
+  PacketBuffer d_response;
+};
+
+static std::deque<ExpectedStep> s_steps;  
+static std::map<uint16_t, ExpectedData> s_responses;
+
+std::ostream& operator<<(std::ostream &os, const ExpectedStep::ExpectedRequest d);
+
+std::ostream& operator<<(std::ostream &os, const ExpectedStep::ExpectedRequest d)
+{
+  static const std::vector<std::string> requests = { "handshake with client", "read from client", "write to client", "close connection to client", "connect to the backend", "read from the backend", "write to the backend", "close connection to backend" };
+  os<<requests.at(static_cast<size_t>(d));
+  return os;
+}
+
+struct DOHConnection
+{
+  DOHConnection(): d_session(std::unique_ptr<nghttp2_session, void (*)(nghttp2_session*)>(nullptr, nghttp2_session_del))
+  {
+    nghttp2_session_callbacks* cbs = nullptr;
+    nghttp2_session_callbacks_new(&cbs);
+    std::unique_ptr<nghttp2_session_callbacks, void (*)(nghttp2_session_callbacks*)> callbacks(cbs, nghttp2_session_callbacks_del);
+    cbs = nullptr;
+    nghttp2_session_callbacks_set_send_callback(callbacks.get(), send_callback);
+    nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks.get(), on_frame_recv_callback);
+    nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks.get(), on_data_chunk_recv_callback);
+    nghttp2_session_callbacks_set_on_stream_close_callback(callbacks.get(), on_stream_close_callback);
+    nghttp2_session* sess = nullptr;
+    nghttp2_session_server_new(&sess, callbacks.get(), this);
+    d_session = std::unique_ptr<nghttp2_session, void (*)(nghttp2_session*)>(sess, nghttp2_session_del);
+
+    nghttp2_settings_entry iv[1] = {
+      {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
+    nghttp2_submit_settings(d_session.get(), NGHTTP2_FLAG_NONE, iv, sizeof(iv)/sizeof(*iv));
+  }
+
+  PacketBuffer d_serverOutBuffer;
+  std::map<uint32_t,PacketBuffer> d_queries;
+  std::map<uint32_t,PacketBuffer> d_responses;
+  std::unique_ptr<nghttp2_session,void(*)(nghttp2_session*)> d_session;
+  /* used to replace the stream ID in outgoing frames. Ugly but the library does not let us
+     test weird cases without that */
+  std::map<uint32_t,uint32_t> d_idMapping;
+
+  size_t submitIncoming(const PacketBuffer& data, size_t pos, size_t toWrite)
+  {
+    ssize_t readlen = nghttp2_session_mem_recv(d_session.get(), &data.at(pos), toWrite);
+    if (readlen < 0) {
+      throw("Fatal error while submitting: " + std::string(nghttp2_strerror(static_cast<int>(readlen))));
+    }
+
+    /* just in case, see if we have anything to send */
+    int rv = nghttp2_session_send(d_session.get());
+    if (rv != 0) {
+      throw("Fatal error while sending: " + std::string(nghttp2_strerror(rv)));
+    }
+
+    return readlen;
+  }
+
+  void submitResponse(uint32_t streamId, PacketBuffer& data)
+  {
+    const nghttp2_nv hdrs[] = {(uint8_t*)":status", (uint8_t*)"200", sizeof(":status")-1, sizeof("200")-1,  NGHTTP2_NV_FLAG_NONE};
+    nghttp2_data_provider dataProvider;
+    dataProvider.source.ptr = &data;
+    dataProvider.read_callback = [](nghttp2_session* session, int32_t stream_id, uint8_t* buf, size_t length, uint32_t* data_flags, nghttp2_data_source* source, void* user_data) -> ssize_t {
+      auto buffer = reinterpret_cast<PacketBuffer*>(source->ptr);
+      size_t toCopy = 0;
+      if (buffer->size() > 0) {
+        toCopy = length > buffer->size() ? buffer->size() : length;
+        memcpy(buf, &buffer->at(0), toCopy);
+        buffer->erase(buffer->begin(), buffer->begin() + toCopy);
+      }
+
+      if (buffer->size() == 0) {
+        *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+      }
+      // cerr<<"submitting response data of size "<<toCopy<<" for stream "<<stream_id<<endl;
+      return toCopy;
+    };
+
+    int rv = nghttp2_submit_response(d_session.get(), streamId, hdrs, sizeof(hdrs)/sizeof(*hdrs), &dataProvider);
+    // cerr<<"Submitting response for stream ID "<<streamId<<": "<<rv<<endl;
+    BOOST_CHECK_EQUAL(rv, 0);
+  }
+
+  void submitError(uint32_t streamId, uint16_t status, const std::string& msg)
+  {
+    const std::string statusStr = std::to_string(status);
+    const nghttp2_nv hdrs[] = {(uint8_t*)":status", (uint8_t*)statusStr.c_str(), sizeof(":status")-1, statusStr.size(),  NGHTTP2_NV_FLAG_NONE};
+
+    int rv = nghttp2_submit_response(d_session.get(), streamId, hdrs, sizeof(hdrs)/sizeof(*hdrs), nullptr);
+    BOOST_CHECK_EQUAL(rv, 0);
+  }
+
+  void submitGoAway()
+  {
+    int rv = nghttp2_submit_goaway(d_session.get(), NGHTTP2_FLAG_NONE, 0, NGHTTP2_INTERNAL_ERROR, nullptr, 0);
+    BOOST_CHECK_EQUAL(rv, 0);
+  }
+
+private:
+  static ssize_t send_callback(nghttp2_session* session, const uint8_t* data, size_t length, int flags, void* user_data)
+  {
+    DOHConnection* conn = reinterpret_cast<DOHConnection*>(user_data);
+    // cerr<<"inserting "<<length<<" bytes into the server output buffer of size "<<conn->d_serverOutBuffer.size()<<endl;
+    if (!conn->d_idMapping.empty() && length > 9) {
+      /* frame type == DATA */
+      if (data[3] == NGHTTP2_DATA) {
+        uint32_t streamId = 0;
+        memcpy(&streamId, &data[5], sizeof(streamId));
+        const auto it = conn->d_idMapping.find(ntohl(streamId));
+        if (it != conn->d_idMapping.end()) {
+          streamId = htonl(it->second);
+          std::vector<uint8_t> editedData(length);
+          std::copy(data, data + length, editedData.begin());
+          memcpy(&editedData.at(5), &streamId, sizeof(streamId));
+          conn->d_serverOutBuffer.insert(conn->d_serverOutBuffer.end(), editedData.data(), editedData.data() + length);
+          return static_cast<ssize_t>(editedData.size());
+        }
+      }
+    }
+
+    conn->d_serverOutBuffer.insert(conn->d_serverOutBuffer.end(), data, data + length);
+    return static_cast<ssize_t>(length);
+  }
+
+  static int on_frame_recv_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data)
+  {
+    DOHConnection* conn = reinterpret_cast<DOHConnection*>(user_data);
+    // cerr<<"Frame type is "<<std::to_string(frame->hd.type)<<endl;
+    if ((frame->hd.type == NGHTTP2_HEADERS || frame->hd.type == NGHTTP2_DATA) && frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+#if 0
+      auto stream_data = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
+      /* For DATA and HEADERS frame, this callback may be called after on_stream_close_callback. Check that stream still alive. */
+      if (stream_data == nullptr) {
+        cerr<<"unable to find stream data!"<<endl;
+        return 0;
+      }
+#endif
+
+      auto& query = conn->d_queries.at(frame->hd.stream_id);
+      BOOST_REQUIRE_GT(query.size(), sizeof(dnsheader));
+      auto dh = reinterpret_cast<const dnsheader*>(query.data());
+      uint16_t id = ntohs(dh->id);
+      // cerr<<"got query ID "<<id<<endl;
+
+      const auto& expected = s_responses.at(id);
+      BOOST_REQUIRE_EQUAL(expected.d_query.size(), query.size());
+      for (size_t idx = 0; idx < query.size(); idx++) {
+        if (expected.d_query.at(idx) != query.at(idx)) {
+          cerr<<"Mismatch at offset "<<idx<<", expected "<<std::to_string(query.at(idx))<<" got "<<std::to_string(expected.d_query.at(idx))<<endl;
+          BOOST_CHECK(false);
+        }
+      }
+
+      DNSName qname(reinterpret_cast<const char*>(query.data()), query.size(), sizeof(dnsheader), false);
+      if (qname == DNSName("goaway.powerdns.com.")) {
+        conn->submitGoAway();
+      }
+      else if (qname == DNSName("500.powerdns.com.") && (id % 2) == 0) {
+        /* we return a 500 on the first query only */
+        conn->submitError(frame->hd.stream_id, 500, "Server failure");
+      }
+      else if (qname == DNSName("wrong-stream-id.powerdns.com.") && (id % 2) == 0) {
+        /* we return a wrong stremad ID on the first query only */
+        BOOST_CHECK_EQUAL(frame->hd.stream_id, 1U);
+        conn->d_responses[frame->hd.stream_id] = expected.d_response;
+        /* use an invalid stream ID! */
+        conn->d_idMapping[frame->hd.stream_id] = frame->hd.stream_id + 4;
+        conn->submitResponse(frame->hd.stream_id, conn->d_responses.at(frame->hd.stream_id));
+      }
+      else {
+        conn->d_responses[frame->hd.stream_id] = expected.d_response;
+        conn->submitResponse(frame->hd.stream_id, conn->d_responses.at(frame->hd.stream_id));
+      }
+      conn->d_queries.erase(frame->hd.stream_id);
+    }
+
+    return 0;
+  }
+
+  static int on_data_chunk_recv_callback(nghttp2_session* session, uint8_t flags, int32_t stream_id, const uint8_t* data, size_t len, void* user_data)
+  {
+    DOHConnection* conn = reinterpret_cast<DOHConnection*>(user_data);
+    // cerr<<"in "<<__PRETTY_FUNCTION__<<endl;
+    auto& query = conn->d_queries[stream_id];
+    query.insert(query.end(), data, data + len);
+    // cerr<<"out "<<__PRETTY_FUNCTION__<<endl;
+    return 0;
+  }
+
+  static int on_stream_close_callback(nghttp2_session* session, int32_t stream_id, uint32_t error_code, void* user_data)
+  {
+    //DOHConnection* conn = reinterpret_cast<DOHConnection*>(user_data);
+
+    if (error_code == 0) {
+      return 0;
+    }
+
+    return 0;
+  }
+
+};
+
+static std::map<int, std::unique_ptr<DOHConnection>> s_connectionBuffers;
+
+class MockupTLSConnection : public TLSConnection
+{
+public:
+  MockupTLSConnection(int descriptor, bool client = false): d_descriptor(descriptor), d_client(client)
+  {
+    s_connectionBuffers[d_descriptor] = std::make_unique<DOHConnection>();
+  }
+
+  ~MockupTLSConnection() { }
+
+  IOState tryHandshake() override
+  {
+    auto step = getStep();
+    BOOST_REQUIRE_EQUAL(step.request, ExpectedStep::ExpectedRequest::handshakeClient);
+
+    return step.nextState;
+  }
+
+  IOState tryWrite(const PacketBuffer& buffer, size_t& pos, size_t toWrite) override
+  {
+    auto& conn = s_connectionBuffers.at(d_descriptor);
+    auto step = getStep();
+    BOOST_REQUIRE_EQUAL(step.request, !d_client ? ExpectedStep::ExpectedRequest::writeToClient : ExpectedStep::ExpectedRequest::writeToBackend);
+
+    if (step.bytes == 0) {
+      if (step.nextState == IOState::NeedWrite) {
+        return step.nextState;
+      }
+      throw std::runtime_error("Remote host closed the connection");
+    }
+
+    toWrite -= pos;
+    BOOST_REQUIRE_GE(buffer.size(), pos + toWrite);
+
+    if (step.bytes < toWrite) {
+      toWrite = step.bytes;
+    }
+
+    conn->submitIncoming(buffer, pos, toWrite);
+    pos += toWrite;
+
+    return step.nextState;
+  }
+
+  IOState tryRead(PacketBuffer& buffer, size_t& pos, size_t toRead, bool allowIncomplete=false) override
+  {
+    auto& conn = s_connectionBuffers.at(d_descriptor);
+    auto step = getStep();
+    BOOST_REQUIRE_EQUAL(step.request, !d_client ? ExpectedStep::ExpectedRequest::readFromClient : ExpectedStep::ExpectedRequest::readFromBackend);
+
+    if (step.bytes == 0) {
+      if (step.nextState == IOState::NeedRead) {
+        return step.nextState;
+      }
+      throw std::runtime_error("Remote host closed the connection");
+    }
+
+    auto& externalBuffer = conn->d_serverOutBuffer;
+    toRead -= pos;
+
+    if (step.bytes < toRead) {
+      toRead = step.bytes;
+    }
+    if (allowIncomplete) {
+      if (toRead > externalBuffer.size()) {
+        toRead = externalBuffer.size();
+      }
+    }
+    else {
+      BOOST_REQUIRE_GE(externalBuffer.size(), toRead);
+    }
+
+    BOOST_REQUIRE_GE(buffer.size(), toRead);
+
+    // cerr<<"in server try read, adding "<<toRead<<" bytes from the buffer of size "<<externalBuffer.size()<<" at position "<<pos<<", buffer had a size of "<<buffer.size()<<endl;
+    std::copy(externalBuffer.begin(), externalBuffer.begin() + toRead, buffer.begin() + pos);
+    pos += toRead;
+    externalBuffer.erase(externalBuffer.begin(), externalBuffer.begin() + toRead);
+    // cerr<<"external buffer has "<<externalBuffer.size()<<" remaining"<<endl;
+
+    return step.nextState;
+  }
+
+  IOState tryConnect(bool fastOpen, const ComboAddress& remote) override
+  {
+    auto step = getStep();
+    BOOST_REQUIRE_EQUAL(step.request, ExpectedStep::ExpectedRequest::connectToBackend);
+
+    return step.nextState;
+  }
+
+  void close() override
+  {
+    auto step = getStep();
+    BOOST_REQUIRE_EQUAL(step.request, !d_client ? ExpectedStep::ExpectedRequest::closeClient : ExpectedStep::ExpectedRequest::closeBackend);
+  }
+
+  bool hasBufferedData() const override
+  {
+    return false;
+  }
+
+  std::string getServerNameIndication() const override
+  {
+    return "";
+  }
+
+  std::vector<uint8_t> getNextProtocol() const override
+  {
+    return std::vector<uint8_t>();
+  }
+
+  LibsslTLSVersion getTLSVersion() const override
+  {
+    return LibsslTLSVersion::TLS13;
+  }
+
+  bool hasSessionBeenResumed() const override
+  {
+    return false;
+  }
+
+  std::vector<std::unique_ptr<TLSSession>> getSessions() override
+  {
+    return {};
+  }
+
+  void setSession(std::unique_ptr<TLSSession>& session) override
+  {
+  }
+
+  /* unused in that context, don't bother */
+  void doHandshake() override
+  {
+  }
+
+  void connect(bool fastOpen, const ComboAddress& remote, const struct timeval& timeout) override
+  {
+  }
+
+  size_t read(void* buffer, size_t bufferSize, const struct timeval&readTimeout, const struct timeval& totalTimeout={0,0}, bool allowIncomplete=false) override
+  {
+    return 0;
+  }
+
+  size_t write(const void* buffer, size_t bufferSize, const struct timeval& writeTimeout) override
+  {
+    return 0;
+  }
+private:
+  ExpectedStep getStep() const
+  {
+    BOOST_REQUIRE(!s_steps.empty());
+    auto step = s_steps.front();
+    s_steps.pop_front();
+
+    if (step.cb) {
+      step.cb(d_descriptor, step);
+    }
+
+    return step;
+  }
+
+  const int d_descriptor;
+  bool d_client{false};
+};
+
+class MockupTLSCtx : public TLSCtx
+{
+public:
+  ~MockupTLSCtx()
+  {
+  }
+
+  std::unique_ptr<TLSConnection> getConnection(int socket, const struct timeval& timeout, time_t now) override
+  {
+    return std::make_unique<MockupTLSConnection>(socket);
+  }
+
+  std::unique_ptr<TLSConnection> getClientConnection(const std::string& host, int socket, const struct timeval& timeout) override
+  {
+    return std::make_unique<MockupTLSConnection>(socket, true);
+  }
+
+  void rotateTicketsKey(time_t now) override
+  {
+  }
+
+  size_t getTicketsKeysCount() override
+  {
+    return 0;
+  }
+
+  std::string getName() const override
+  {
+    return "Mockup TLS";
+  }
+};
+
+class MockupFDMultiplexer : public FDMultiplexer
+{
+public:
+  MockupFDMultiplexer()
+  {
+  }
+
+  ~MockupFDMultiplexer()
+  {
+  }
+
+  int run(struct timeval* tv, int timeout=500) override
+  {
+    int ret = 0;
+
+    gettimeofday(tv, nullptr); // MANDATORY
+
+    /* 'ready' might be altered by a callback while we are iterating */
+    const auto readyFDs = ready;
+    for (const auto fd : readyFDs) {
+      {
+        const auto& it = d_readCallbacks.find(fd);
+
+        if (it != d_readCallbacks.end()) {
+          it->d_callback(it->d_fd, it->d_parameter);
+        }
+      }
+
+      {
+        const auto& it = d_writeCallbacks.find(fd);
+
+        if (it != d_writeCallbacks.end()) {
+          it->d_callback(it->d_fd, it->d_parameter);
+        }
+      }
+    }
+
+    return ret;
+  }
+
+  void getAvailableFDs(std::vector<int>& fds, int timeout) override
+  {
+  }
+
+  void addFD(int fd, FDMultiplexer::EventKind kind) override
+  {
+  }
+
+  void removeFD(int fd, FDMultiplexer::EventKind) override
+  {
+  }
+
+  string getName() const override
+  {
+    return "mockup";
+  }
+
+  void setReady(int fd)
+  {
+    ready.insert(fd);
+  }
+
+  void setNotReady(int fd)
+  {
+    ready.erase(fd);
+  }
+
+private:
+  std::set<int> ready;
+};
+
+class MockupQuerySender : public TCPQuerySender
+{
+public:
+  bool active() const override
+  {
+    return true;
+  }
+
+  const ClientState* getClientState() const override
+  {
+    return nullptr;
+  }
+
+  void handleResponse(const struct timeval& now, TCPResponse&& response) override
+  {
+    if (d_customHandler) {
+      d_customHandler(d_id, now, std::move(response));
+      return;
+    }
+
+    BOOST_REQUIRE_GT(response.d_buffer.size(), sizeof(dnsheader));
+    auto dh = reinterpret_cast<const dnsheader*>(response.d_buffer.data());
+    uint16_t id = ntohs(dh->id);
+
+    BOOST_REQUIRE_EQUAL(id, d_id);
+    const auto& expected = s_responses.at(id);
+    BOOST_REQUIRE_EQUAL(expected.d_response.size(), response.d_buffer.size());
+    for (size_t idx = 0; idx < response.d_buffer.size(); idx++) {
+      if (expected.d_response.at(idx) != response.d_buffer.at(idx)) {
+        cerr<<"Mismatch at offset "<<idx<<", expected "<<std::to_string(response.d_buffer.at(idx))<<" got "<<std::to_string(expected.d_response.at(idx))<<endl;
+        BOOST_CHECK(false);
+      }
+    }
+
+    if (expected.d_response != response.d_buffer) {
+      BOOST_REQUIRE(false);
+    }
+    d_valid = true;
+  }
+
+  void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override
+  {
+  }
+
+  void notifyIOError(IDState&& query, const struct timeval& now) override
+  {
+    d_error = true;
+  }
+
+  std::function<void(uint16_t id, const struct timeval& now, TCPResponse&& response)> d_customHandler;
+  uint16_t d_id{0};
+  bool d_valid{false};
+  bool d_error{false};
+};
+
+static std::unique_ptr<FDMultiplexer> s_mplexer;
+
+struct TestFixture
+{
+  TestFixture()
+  {
+    s_steps.clear();
+    s_responses.clear();
+    s_mplexer = std::unique_ptr<FDMultiplexer>(new MockupFDMultiplexer());
+  }
+  ~TestFixture()
+  {
+    clearH2Connections();
+    s_steps.clear();
+    s_responses.clear();
+    s_mplexer.reset();
+  } 
+};
+
+BOOST_FIXTURE_TEST_CASE(test_SingleQuery, TestFixture)
+{
+  ComboAddress local("192.0.2.1:80");
+  ClientState localCS(local, true, false, false, "", {});
+  auto tlsCtx = std::make_shared<MockupTLSCtx>();
+  localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+  struct timeval now;
+  gettimeofday(&now, nullptr);
+
+  size_t counter = 1;
+  DNSName name("powerdns.com.");
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+  pwQ.getHeader()->rd = 1;
+  pwQ.getHeader()->id = htons(counter);
+
+  PacketBuffer response;
+  GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+  pwR.getHeader()->qr = 1;
+  pwR.getHeader()->rd = 1;
+  pwR.getHeader()->ra = 1;
+  pwR.getHeader()->id = htons(counter);
+  pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+  pwR.xfr32BitInt(0x01020304);
+  pwR.commit();
+
+  s_responses[counter] = {query, response};
+
+  auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+  backend->d_tlsCtx = tlsCtx;
+  backend->d_tlsSubjectName = "backend.powerdns.com";
+  backend->d_dohPath = "/dns-query";
+  backend->d_addXForwardedHeaders = true;
+
+  auto sender = std::make_shared<MockupQuerySender>();
+  sender->d_id = counter;
+  InternalQuery internalQuery(std::move(query), IDState());
+
+  s_steps = {
+    { ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done },
+    /* opening */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), [](int desc, const ExpectedStep& step) {
+      /* set the outgoing descriptor (backend connection) as ready */
+      dynamic_cast<MockupFDMultiplexer*>(s_mplexer.get())->setReady(desc);
+    } },
+    /* read settings, headers and response from the server */
+    { ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* acknowledge settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
+  };
+
+  auto sliced = std::shared_ptr<TCPQuerySender>(sender);
+  bool result = sendH2Query(backend, s_mplexer, sliced, std::move(internalQuery), false);
+  BOOST_CHECK_EQUAL(result, true);
+
+  while (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0) {
+    s_mplexer->run(&now);
+  }
+  BOOST_CHECK_EQUAL(sender->d_valid, true);
+}
+
+BOOST_FIXTURE_TEST_CASE(test_ConcurrentQueries, TestFixture)
+{
+  ComboAddress local("192.0.2.1:80");
+  ClientState localCS(local, true, false, false, "", {});
+  auto tlsCtx = std::make_shared<MockupTLSCtx>();
+  localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+  struct timeval now;
+  gettimeofday(&now, nullptr);
+
+  auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+  backend->d_tlsCtx = tlsCtx;
+  backend->d_tlsSubjectName = "backend.powerdns.com";
+  backend->d_dohPath = "/dns-query";
+  backend->d_addXForwardedHeaders = true;
+
+  size_t numberOfQueries = 2;
+  std::vector<std::pair<std::shared_ptr<MockupQuerySender>, InternalQuery>> queries;
+  for (size_t counter = 0; counter < numberOfQueries; counter++) {
+    DNSName name("powerdns.com.");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+    pwQ.getHeader()->id = htons(counter);
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->id = htons(counter);
+    pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfr32BitInt(0x01020304);
+    pwR.commit();
+
+    s_responses[counter] = {query, response};
+
+    auto sender = std::make_shared<MockupQuerySender>();
+    sender->d_id = counter;
+    InternalQuery internalQuery(std::move(query), IDState());
+    queries.push_back({std::move(sender), std::move(internalQuery)});
+  }
+
+  s_steps = {
+    { ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done },
+    /* opening */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), [](int desc, const ExpectedStep& step) {
+      /* set the outgoing descriptor (backend connection) as ready */
+      dynamic_cast<MockupFDMultiplexer*>(s_mplexer.get())->setReady(desc);
+    } },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), [](int desc, const ExpectedStep& step) {
+      /* set the outgoing descriptor (backend connection) as ready */
+      dynamic_cast<MockupFDMultiplexer*>(s_mplexer.get())->setReady(desc);
+    } },
+    /* read settings, headers and responses from the server */
+    { ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* acknowledge settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
+  };
+
+  for (auto& query : queries) {
+    auto sliced = std::static_pointer_cast<TCPQuerySender>(query.first);
+    bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false);
+    BOOST_CHECK_EQUAL(result, true);
+  }
+
+  while (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0) {
+    s_mplexer->run(&now);
+  }
+
+  for (auto& query : queries) {
+    BOOST_CHECK_EQUAL(query.first->d_valid, true);
+  }
+}
+
+BOOST_FIXTURE_TEST_CASE(test_ConnectionReuse, TestFixture)
+{
+  ComboAddress local("192.0.2.1:80");
+  ClientState localCS(local, true, false, false, "", {});
+  auto tlsCtx = std::make_shared<MockupTLSCtx>();
+  localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+  struct timeval now;
+  gettimeofday(&now, nullptr);
+
+  auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+  backend->d_tlsCtx = tlsCtx;
+  backend->d_tlsSubjectName = "backend.powerdns.com";
+  backend->d_dohPath = "/dns-query";
+  backend->d_addXForwardedHeaders = true;
+
+  size_t numberOfQueries = 2;
+  std::vector<std::pair<std::shared_ptr<MockupQuerySender>, InternalQuery>> queries;
+  for (size_t counter = 0; counter < numberOfQueries; counter++) {
+    DNSName name("powerdns.com.");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+    pwQ.getHeader()->id = htons(counter);
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->id = htons(counter);
+    pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfr32BitInt(0x01020304);
+    pwR.commit();
+
+    s_responses[counter] = {query, response};
+
+    auto sender = std::make_shared<MockupQuerySender>();
+    sender->d_id = counter;
+    InternalQuery internalQuery(std::move(query), IDState());
+    queries.push_back({std::move(sender), std::move(internalQuery)});
+  }
+
+  s_steps = {
+    { ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done },
+    /* opening */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), [](int desc, const ExpectedStep& step) {
+      /* set the outgoing descriptor (backend connection) as ready */
+      dynamic_cast<MockupFDMultiplexer*>(s_mplexer.get())->setReady(desc);
+    } },
+    /* read settings, headers and responses from the server */
+    { ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* acknowledge settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), [](int desc, const ExpectedStep& step) {
+      /* set the outgoing descriptor (backend connection) as ready */
+      dynamic_cast<MockupFDMultiplexer*>(s_mplexer.get())->setReady(desc);
+    } },
+    /* read settings, headers and responses from the server */
+    { ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
+  };
+
+  {
+    auto& query = queries.at(0);
+    auto sliced = std::static_pointer_cast<TCPQuerySender>(query.first);
+    bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false);
+    BOOST_CHECK_EQUAL(result, true);
+
+    while (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0) {
+      s_mplexer->run(&now);
+    }
+
+    BOOST_CHECK_EQUAL(query.first->d_valid, true);
+  }
+
+  {
+    auto& query = queries.at(1);
+    auto sliced = std::static_pointer_cast<TCPQuerySender>(query.first);
+    bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false);
+    BOOST_CHECK_EQUAL(result, true);
+
+    while (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0) {
+      s_mplexer->run(&now);
+    }
+
+    BOOST_CHECK_EQUAL(query.first->d_valid, true);
+  }
+}
+
+BOOST_FIXTURE_TEST_CASE(test_InvalidDNSAnswer, TestFixture)
+{
+  ComboAddress local("192.0.2.1:80");
+  ClientState localCS(local, true, false, false, "", {});
+  auto tlsCtx = std::make_shared<MockupTLSCtx>();
+  localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+  struct timeval now;
+  gettimeofday(&now, nullptr);
+
+  size_t counter = 1;
+  DNSName name("powerdns.com.");
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+  pwQ.getHeader()->rd = 1;
+  pwQ.getHeader()->id = htons(counter);
+
+  PacketBuffer response;
+  GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+  pwR.getHeader()->qr = 1;
+  pwR.getHeader()->rd = 1;
+  pwR.getHeader()->ra = 1;
+  pwR.getHeader()->id = htons(counter);
+  pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+  pwR.xfr32BitInt(0x01020304);
+  pwR.commit();
+
+  /* TRUNCATE the answer */
+  response.resize(11);
+  s_responses[counter] = {query, response};
+
+  auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+  backend->d_tlsCtx = tlsCtx;
+  backend->d_tlsSubjectName = "backend.powerdns.com";
+  backend->d_dohPath = "/dns-query";
+  backend->d_addXForwardedHeaders = true;
+
+  auto sender = std::make_shared<MockupQuerySender>();
+  sender->d_id = counter;
+  sender->d_customHandler = [](uint16_t id, const struct timeval&, TCPResponse&& resp) {
+    BOOST_CHECK_EQUAL(resp.d_buffer.size(), 11U);
+    /* simulate an exception, since DoH and UDP frontends will process the query right away,
+       while TCP and DoT will first pass it back to the TCP worker thread */
+    throw std::runtime_error("Invalid response");
+  };
+  InternalQuery internalQuery(std::move(query), IDState());
+
+  s_steps = {
+    { ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done },
+    /* opening */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), [](int desc, const ExpectedStep& step) {
+      /* set the outgoing descriptor (backend connection) as ready */
+      dynamic_cast<MockupFDMultiplexer*>(s_mplexer.get())->setReady(desc);
+    } },
+    /* read settings, headers and response from the server */
+    { ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* acknowledge settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
+  };
+
+  auto sliced = std::shared_ptr<TCPQuerySender>(sender);
+  bool result = sendH2Query(backend, s_mplexer, sliced, std::move(internalQuery), false);
+  BOOST_CHECK_EQUAL(result, true);
+
+  while (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0) {
+    s_mplexer->run(&now);
+  }
+  BOOST_CHECK_EQUAL(sender->d_valid, false);
+}
+
+BOOST_FIXTURE_TEST_CASE(test_TimeoutWhileWriting, TestFixture)
+{
+  ComboAddress local("192.0.2.1:80");
+  ClientState localCS(local, true, false, false, "", {});
+  auto tlsCtx = std::make_shared<MockupTLSCtx>();
+  localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+  struct timeval now;
+  gettimeofday(&now, nullptr);
+
+  auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+  backend->d_tlsCtx = tlsCtx;
+  backend->d_tlsSubjectName = "backend.powerdns.com";
+  backend->d_dohPath = "/dns-query";
+  backend->d_addXForwardedHeaders = true;
+
+  size_t numberOfQueries = 2;
+  std::vector<std::pair<std::shared_ptr<MockupQuerySender>, InternalQuery>> queries;
+  for (size_t counter = 0; counter < numberOfQueries; counter++) {
+    DNSName name("powerdns.com.");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+    pwQ.getHeader()->id = htons(counter);
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->id = htons(counter);
+    pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfr32BitInt(0x01020304);
+    pwR.commit();
+
+    s_responses[counter] = {query, response};
+
+    auto sender = std::make_shared<MockupQuerySender>();
+    sender->d_id = counter;
+    InternalQuery internalQuery(std::move(query), IDState());
+    queries.push_back({std::move(sender), std::move(internalQuery)});
+  }
+
+  bool timeout = false;
+  s_steps = {
+    { ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done },
+    /* opening */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::NeedWrite, std::numeric_limits<size_t>::max(), [&timeout](int desc, const ExpectedStep& step) {
+      timeout = true;
+    } },
+    { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
+  };
+
+  for (auto& query : queries) {
+    auto sliced = std::static_pointer_cast<TCPQuerySender>(query.first);
+    bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false);
+    BOOST_CHECK_EQUAL(result, true);
+  }
+
+  while (!timeout && (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0)) {
+    s_mplexer->run(&now);
+  }
+
+  struct timeval later = now;
+  later.tv_sec += backend->tcpSendTimeout + 1;
+
+  auto expiredConns = handleH2Timeouts(*s_mplexer, later);
+  BOOST_CHECK_EQUAL(expiredConns, 1U);
+
+  for (auto& query : queries) {
+    BOOST_CHECK_EQUAL(query.first->d_valid, false);
+    BOOST_CHECK_EQUAL(query.first->d_error, true);
+  }
+
+  BOOST_CHECK_EQUAL(clearH2Connections(), 0U);
+}
+
+BOOST_FIXTURE_TEST_CASE(test_TimeoutWhileReading, TestFixture)
+{
+  ComboAddress local("192.0.2.1:80");
+  ClientState localCS(local, true, false, false, "", {});
+  auto tlsCtx = std::make_shared<MockupTLSCtx>();
+  localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+  struct timeval now;
+  gettimeofday(&now, nullptr);
+
+  auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+  backend->d_tlsCtx = tlsCtx;
+  backend->d_tlsSubjectName = "backend.powerdns.com";
+  backend->d_dohPath = "/dns-query";
+  backend->d_addXForwardedHeaders = true;
+
+  size_t numberOfQueries = 2;
+  std::vector<std::pair<std::shared_ptr<MockupQuerySender>, InternalQuery>> queries;
+  for (size_t counter = 0; counter < numberOfQueries; counter++) {
+    DNSName name("powerdns.com.");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+    pwQ.getHeader()->id = htons(counter);
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->id = htons(counter);
+    pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfr32BitInt(0x01020304);
+    pwR.commit();
+
+    s_responses[counter] = {query, response};
+
+    auto sender = std::make_shared<MockupQuerySender>();
+    sender->d_id = counter;
+    InternalQuery internalQuery(std::move(query), IDState());
+    queries.push_back({std::move(sender), std::move(internalQuery)});
+  }
+
+  bool timeout = false;
+  s_steps = {
+    { ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done },
+    /* opening */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), [&timeout](int desc, const ExpectedStep& step) {
+      /* set the timeout flag now, since the timeout occurs while waiting for the descriptor to become readable */
+      timeout = true;
+    } },
+    { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
+};
+
+  for (auto& query : queries) {
+    auto sliced = std::static_pointer_cast<TCPQuerySender>(query.first);
+    bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false);
+    BOOST_CHECK_EQUAL(result, true);
+  }
+
+  while (!timeout && (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0)) {
+    s_mplexer->run(&now);
+  }
+
+  struct timeval later = now;
+  later.tv_sec += backend->tcpRecvTimeout + 1;
+
+  auto expiredConns = handleH2Timeouts(*s_mplexer, later);
+  BOOST_CHECK_EQUAL(expiredConns, 1U);
+
+  for (auto& query : queries) {
+    BOOST_CHECK_EQUAL(query.first->d_valid, false);
+    BOOST_CHECK_EQUAL(query.first->d_error, true);
+  }
+  BOOST_CHECK_EQUAL(clearH2Connections(), 0U);
+}
+
+BOOST_FIXTURE_TEST_CASE(test_ShortWrite, TestFixture)
+{
+  ComboAddress local("192.0.2.1:80");
+  ClientState localCS(local, true, false, false, "", {});
+  auto tlsCtx = std::make_shared<MockupTLSCtx>();
+  localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+  struct timeval now;
+  gettimeofday(&now, nullptr);
+
+  auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+  backend->d_tlsCtx = tlsCtx;
+  backend->d_tlsSubjectName = "backend.powerdns.com";
+  backend->d_dohPath = "/dns-query";
+  backend->d_addXForwardedHeaders = true;
+
+  size_t numberOfQueries = 2;
+  std::vector<std::pair<std::shared_ptr<MockupQuerySender>, InternalQuery>> queries;
+  for (size_t counter = 0; counter < numberOfQueries; counter++) {
+    DNSName name("powerdns.com.");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+    pwQ.getHeader()->id = htons(counter);
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->id = htons(counter);
+    pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfr32BitInt(0x01020304);
+    pwR.commit();
+
+    s_responses[counter] = {query, response};
+
+    auto sender = std::make_shared<MockupQuerySender>();
+    sender->d_id = counter;
+    InternalQuery internalQuery(std::move(query), IDState());
+    queries.push_back({std::move(sender), std::move(internalQuery)});
+  }
+
+  s_steps = {
+    { ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done },
+    /* opening */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::NeedWrite, 2, [](int desc, const ExpectedStep& step) {
+      /* set the outgoing descriptor (backend connection) as ready */
+      dynamic_cast<MockupFDMultiplexer*>(s_mplexer.get())->setReady(desc);
+    } },
+    /* settings (second attempt) + headers + data + headers (second query) + data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), },
+    /* read settings, headers and responses from the server */
+    { ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* acknowledge settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
+  };
+
+  for (auto& query : queries) {
+    auto sliced = std::static_pointer_cast<TCPQuerySender>(query.first);
+    bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false);
+    BOOST_CHECK_EQUAL(result, true);
+  }
+
+  while (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0) {
+    s_mplexer->run(&now);
+  }
+
+  for (auto& query : queries) {
+    BOOST_CHECK_EQUAL(query.first->d_valid, true);
+  }
+
+  BOOST_CHECK_EQUAL(clearH2Connections(), 1U);
+}
+
+BOOST_FIXTURE_TEST_CASE(test_ShortRead, TestFixture)
+{
+  ComboAddress local("192.0.2.1:80");
+  ClientState localCS(local, true, false, false, "", {});
+  auto tlsCtx = std::make_shared<MockupTLSCtx>();
+  localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+  struct timeval now;
+  gettimeofday(&now, nullptr);
+
+  auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+  backend->d_tlsCtx = tlsCtx;
+  backend->d_tlsSubjectName = "backend.powerdns.com";
+  backend->d_dohPath = "/dns-query";
+  backend->d_addXForwardedHeaders = true;
+
+  size_t numberOfQueries = 2;
+  std::vector<std::pair<std::shared_ptr<MockupQuerySender>, InternalQuery>> queries;
+  for (size_t counter = 0; counter < numberOfQueries; counter++) {
+    DNSName name("powerdns.com.");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+    pwQ.getHeader()->id = htons(counter);
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->id = htons(counter);
+    pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfr32BitInt(0x01020304);
+    pwR.commit();
+
+    s_responses[counter] = {query, response};
+
+    auto sender = std::make_shared<MockupQuerySender>();
+    sender->d_id = counter;
+    InternalQuery internalQuery(std::move(query), IDState());
+    queries.push_back({std::move(sender), std::move(internalQuery)});
+  }
+
+  s_steps = {
+    { ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done },
+    /* opening */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), [](int desc, const ExpectedStep& step) {
+      /* set the outgoing descriptor (backend connection) as ready */
+      dynamic_cast<MockupFDMultiplexer*>(s_mplexer.get())->setReady(desc);
+    } },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), [](int desc, const ExpectedStep& step) {
+      /* set the outgoing descriptor (backend connection) as ready */
+      dynamic_cast<MockupFDMultiplexer*>(s_mplexer.get())->setReady(desc);
+    } },
+    /* read settings, headers and responses from the server */
+    { ExpectedStep::ExpectedRequest::readFromBackend, IOState::NeedRead, 4 },
+    /* read settings, headers and responses (second attempt) */
+    { ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* acknowledge settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
+  };
+
+  for (auto& query : queries) {
+    auto sliced = std::static_pointer_cast<TCPQuerySender>(query.first);
+    bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false);
+    BOOST_CHECK_EQUAL(result, true);
+  }
+
+  while (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0) {
+    s_mplexer->run(&now);
+  }
+
+  for (auto& query : queries) {
+    BOOST_CHECK_EQUAL(query.first->d_valid, true);
+  }
+
+  BOOST_CHECK_EQUAL(clearH2Connections(), 1U);
+}
+
+BOOST_FIXTURE_TEST_CASE(test_ConnectionClosedWhileReading, TestFixture)
+{
+  ComboAddress local("192.0.2.1:80");
+  ClientState localCS(local, true, false, false, "", {});
+  auto tlsCtx = std::make_shared<MockupTLSCtx>();
+  localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+  struct timeval now;
+  gettimeofday(&now, nullptr);
+
+  auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+  backend->d_tlsCtx = tlsCtx;
+  backend->d_tlsSubjectName = "backend.powerdns.com";
+  backend->d_dohPath = "/dns-query";
+  backend->d_addXForwardedHeaders = true;
+
+  size_t numberOfQueries = 2;
+  std::vector<std::pair<std::shared_ptr<MockupQuerySender>, InternalQuery>> queries;
+  for (size_t counter = 0; counter < numberOfQueries; counter++) {
+    DNSName name("powerdns.com.");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+    pwQ.getHeader()->id = htons(counter);
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->id = htons(counter);
+    pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfr32BitInt(0x01020304);
+    pwR.commit();
+
+    s_responses[counter] = {query, response};
+
+    auto sender = std::make_shared<MockupQuerySender>();
+    sender->d_id = counter;
+    InternalQuery internalQuery(std::move(query), IDState());
+    queries.push_back({std::move(sender), std::move(internalQuery)});
+  }
+
+  s_steps = {
+    { ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done },
+    /* opening */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), [](int desc, const ExpectedStep& step) {
+      /* set the outgoing descriptor (backend connection) as ready */
+      dynamic_cast<MockupFDMultiplexer*>(s_mplexer.get())->setReady(desc);
+    } },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), [](int desc, const ExpectedStep& step) {
+      /* set the outgoing descriptor (backend connection) as ready */
+      dynamic_cast<MockupFDMultiplexer*>(s_mplexer.get())->setReady(desc);
+    } },
+    /* read settings, headers and responses from the server */
+    { ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, 0 },
+    { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
+  };
+
+  for (auto& query : queries) {
+    auto sliced = std::static_pointer_cast<TCPQuerySender>(query.first);
+    bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false);
+    BOOST_CHECK_EQUAL(result, true);
+  }
+
+  while (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0) {
+    s_mplexer->run(&now);
+  }
+
+  for (auto& query : queries) {
+    BOOST_CHECK_EQUAL(query.first->d_valid, false);
+    BOOST_CHECK_EQUAL(query.first->d_error, true);
+  }
+
+  BOOST_CHECK_EQUAL(clearH2Connections(), 0U);
+}
+
+BOOST_FIXTURE_TEST_CASE(test_ConnectionClosedWhileWriting, TestFixture)
+{
+  ComboAddress local("192.0.2.1:80");
+  ClientState localCS(local, true, false, false, "", {});
+  auto tlsCtx = std::make_shared<MockupTLSCtx>();
+  localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+  struct timeval now;
+  gettimeofday(&now, nullptr);
+
+  auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+  backend->d_tlsCtx = tlsCtx;
+  backend->d_tlsSubjectName = "backend.powerdns.com";
+  backend->d_dohPath = "/dns-query";
+  backend->d_addXForwardedHeaders = true;
+
+  size_t numberOfQueries = 2;
+  std::vector<std::pair<std::shared_ptr<MockupQuerySender>, InternalQuery>> queries;
+  for (size_t counter = 0; counter < numberOfQueries; counter++) {
+    DNSName name("powerdns.com.");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+    pwQ.getHeader()->id = htons(counter);
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->id = htons(counter);
+    pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfr32BitInt(0x01020304);
+    pwR.commit();
+
+    s_responses[counter] = {query, response};
+
+    auto sender = std::make_shared<MockupQuerySender>();
+    sender->d_id = counter;
+    InternalQuery internalQuery(std::move(query), IDState());
+    queries.push_back({std::move(sender), std::move(internalQuery)});
+  }
+
+  s_steps = {
+    { ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done },
+    /* opening */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* headers, connection is closed by the backend */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, 0 },
+    { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
+    { ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done },
+    /* opening */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), [](int desc, const ExpectedStep& step) {
+      /* set the outgoing descriptor (backend connection) as ready */
+      dynamic_cast<MockupFDMultiplexer*>(s_mplexer.get())->setReady(desc);
+    } },
+    /* read settings, headers and response from the server */
+    { ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* acknowledge settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
+  };
+
+  for (auto& query : queries) {
+    auto sliced = std::static_pointer_cast<TCPQuerySender>(query.first);
+    bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false);
+    BOOST_CHECK_EQUAL(result, true);
+  }
+
+  while (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0) {
+    s_mplexer->run(&now);
+  }
+
+  BOOST_CHECK_EQUAL(queries.at(0).first->d_valid, false);
+  BOOST_CHECK_EQUAL(queries.at(0).first->d_error, true);
+  BOOST_CHECK_EQUAL(queries.at(1).first->d_valid, true);
+  BOOST_CHECK_EQUAL(queries.at(1).first->d_error, false);
+
+  BOOST_CHECK_EQUAL(clearH2Connections(), 1U);
+}
+
+BOOST_FIXTURE_TEST_CASE(test_GoAwayFromServer, TestFixture)
+{
+  ComboAddress local("192.0.2.1:80");
+  ClientState localCS(local, true, false, false, "", {});
+  auto tlsCtx = std::make_shared<MockupTLSCtx>();
+  localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+  struct timeval now;
+  gettimeofday(&now, nullptr);
+
+  auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+  backend->d_tlsCtx = tlsCtx;
+  backend->d_tlsSubjectName = "backend.powerdns.com";
+  backend->d_dohPath = "/dns-query";
+  backend->d_addXForwardedHeaders = true;
+  /* set the number of reconnection attempts to a low value to not waste time */
+  backend->d_retries = 1;
+
+  size_t numberOfQueries = 2;
+  std::vector<std::pair<std::shared_ptr<MockupQuerySender>, InternalQuery>> queries;
+  for (size_t counter = 0; counter < numberOfQueries; counter++) {
+    DNSName name("goaway.powerdns.com.");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+    pwQ.getHeader()->id = htons(counter);
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->id = htons(counter);
+    pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfr32BitInt(0x01020304);
+    pwR.commit();
+
+    s_responses[counter] = {query, response};
+
+    auto sender = std::make_shared<MockupQuerySender>();
+    sender->d_id = counter;
+    InternalQuery internalQuery(std::move(query), IDState());
+    queries.push_back({std::move(sender), std::move(internalQuery)});
+  }
+
+  s_steps = {
+    { ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done },
+    /* opening */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), [](int desc, const ExpectedStep& step) {
+      /* set the outgoing descriptor (backend connection) as ready */
+      dynamic_cast<MockupFDMultiplexer*>(s_mplexer.get())->setReady(desc);
+    } },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), [](int desc, const ExpectedStep& step) {
+      /* set the outgoing descriptor (backend connection) as ready */
+      dynamic_cast<MockupFDMultiplexer*>(s_mplexer.get())->setReady(desc);
+    } },
+    /* read GO AWAY from the server (1) */
+    { ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    { ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done },
+    /* opening */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), [](int desc, const ExpectedStep& step) {
+      /* set the outgoing descriptor (backend connection) as ready */
+      dynamic_cast<MockupFDMultiplexer*>(s_mplexer.get())->setReady(desc);
+    } },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), [](int desc, const ExpectedStep& step) {
+      /* set the outgoing descriptor (backend connection) as ready */
+      dynamic_cast<MockupFDMultiplexer*>(s_mplexer.get())->setReady(desc);
+    } },
+    /* close the first connection. It happens now because the new connection was set up first, then that one destroyed */
+    { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
+    /* read GO AWAY from the server (1) */
+    { ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
+  };
+
+  for (auto& query : queries) {
+    auto sliced = std::static_pointer_cast<TCPQuerySender>(query.first);
+    bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false);
+    BOOST_CHECK_EQUAL(result, true);
+  }
+
+  while (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0) {
+    s_mplexer->run(&now);
+  }
+
+  for (auto& query : queries) {
+    BOOST_CHECK_EQUAL(query.first->d_valid, false);
+    BOOST_CHECK_EQUAL(query.first->d_error, true);
+  }
+
+  BOOST_CHECK_EQUAL(clearH2Connections(), 0U);
+}
+
+BOOST_FIXTURE_TEST_CASE(test_HTTP500FromServer, TestFixture)
+{
+  ComboAddress local("192.0.2.1:80");
+  ClientState localCS(local, true, false, false, "", {});
+  auto tlsCtx = std::make_shared<MockupTLSCtx>();
+  localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+  struct timeval now;
+  gettimeofday(&now, nullptr);
+
+  auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+  backend->d_tlsCtx = tlsCtx;
+  backend->d_tlsSubjectName = "backend.powerdns.com";
+  backend->d_dohPath = "/dns-query";
+  backend->d_addXForwardedHeaders = true;
+
+  size_t numberOfQueries = 2;
+  std::vector<std::pair<std::shared_ptr<MockupQuerySender>, InternalQuery>> queries;
+  for (size_t counter = 0; counter < numberOfQueries; counter++) {
+    DNSName name("500.powerdns.com.");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+    pwQ.getHeader()->id = htons(counter);
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->id = htons(counter);
+    pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfr32BitInt(0x01020304);
+    pwR.commit();
+
+    s_responses[counter] = {query, response};
+
+    auto sender = std::make_shared<MockupQuerySender>();
+    sender->d_id = counter;
+    InternalQuery internalQuery(std::move(query), IDState());
+    queries.push_back({std::move(sender), std::move(internalQuery)});
+  }
+
+  s_steps = {
+    { ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done },
+    /* opening */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), [](int desc, const ExpectedStep& step) {
+      /* set the outgoing descriptor (backend connection) as ready */
+      dynamic_cast<MockupFDMultiplexer*>(s_mplexer.get())->setReady(desc);
+    } },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), [](int desc, const ExpectedStep& step) {
+      /* set the outgoing descriptor (backend connection) as ready */
+      dynamic_cast<MockupFDMultiplexer*>(s_mplexer.get())->setReady(desc);
+    } },
+    /* read settings, headers and responses from the server */
+    { ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* acknowledge settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
+  };
+
+  for (auto& query : queries) {
+    auto sliced = std::static_pointer_cast<TCPQuerySender>(query.first);
+    bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false);
+    BOOST_CHECK_EQUAL(result, true);
+  }
+
+  while (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0) {
+    s_mplexer->run(&now);
+  }
+
+  BOOST_CHECK_EQUAL(queries.at(0).first->d_valid, false);
+  BOOST_CHECK_EQUAL(queries.at(0).first->d_error, true);
+  BOOST_CHECK_EQUAL(queries.at(1).first->d_valid, true);
+  BOOST_CHECK_EQUAL(queries.at(1).first->d_error, false);
+
+  BOOST_CHECK_EQUAL(clearH2Connections(), 1U);
+}
+
+BOOST_FIXTURE_TEST_CASE(test_WrongStreamID, TestFixture)
+{
+  ComboAddress local("192.0.2.1:80");
+  ClientState localCS(local, true, false, false, "", {});
+  auto tlsCtx = std::make_shared<MockupTLSCtx>();
+  localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+  struct timeval now;
+  gettimeofday(&now, nullptr);
+
+  auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+  backend->d_tlsCtx = tlsCtx;
+  backend->d_tlsSubjectName = "backend.powerdns.com";
+  backend->d_dohPath = "/dns-query";
+  backend->d_addXForwardedHeaders = true;
+
+  size_t numberOfQueries = 2;
+  std::vector<std::pair<std::shared_ptr<MockupQuerySender>, InternalQuery>> queries;
+  for (size_t counter = 0; counter < numberOfQueries; counter++) {
+    DNSName name("wrong-stream-id.powerdns.com.");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+    pwQ.getHeader()->id = htons(counter);
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->id = htons(counter);
+    pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfr32BitInt(0x01020304);
+    pwR.commit();
+
+    s_responses[counter] = {query, response};
+
+    auto sender = std::make_shared<MockupQuerySender>();
+    sender->d_id = counter;
+    InternalQuery internalQuery(std::move(query), IDState());
+    queries.push_back({std::move(sender), std::move(internalQuery)});
+  }
+
+  bool timeout = false;
+  s_steps = {
+    { ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done },
+    /* opening */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), [](int desc, const ExpectedStep& step) {
+      /* set the outgoing descriptor (backend connection) as ready */
+      dynamic_cast<MockupFDMultiplexer*>(s_mplexer.get())->setReady(desc);
+    } },
+    /* headers */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* data */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max(), [](int desc, const ExpectedStep& step) {
+      /* set the outgoing descriptor (backend connection) as ready */
+      dynamic_cast<MockupFDMultiplexer*>(s_mplexer.get())->setReady(desc);
+    } },
+    /* read settings, headers and responses from the server */
+    { ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* acknowledge settings */
+    { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits<size_t>::max() },
+    /* read ends up as a time out since nghttp2 filters the frame with the wrong stream ID */
+    { ExpectedStep::ExpectedRequest::readFromBackend, IOState::NeedRead, 0, [&timeout](int desc, const ExpectedStep& step) {
+      /* set the timeout flag now, since the timeout occurs while waiting for the descriptor to become readable */
+      timeout = true;
+    } },
+    { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
+  };
+
+  for (auto& query : queries) {
+    auto sliced = std::static_pointer_cast<TCPQuerySender>(query.first);
+    bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false);
+    BOOST_CHECK_EQUAL(result, true);
+  }
+
+  while (!timeout && (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0)) {
+    s_mplexer->run(&now);
+  }
+
+  struct timeval later = now;
+  later.tv_sec += backend->tcpRecvTimeout + 1;
+
+  auto expiredConns = handleH2Timeouts(*s_mplexer, later);
+  BOOST_CHECK_EQUAL(expiredConns, 1U);
+
+  BOOST_CHECK_EQUAL(queries.at(0).first->d_valid, false);
+  BOOST_CHECK_EQUAL(queries.at(0).first->d_error, true);
+  BOOST_CHECK_EQUAL(queries.at(1).first->d_valid, false);
+  BOOST_CHECK_EQUAL(queries.at(1).first->d_error, true);
+
+  BOOST_CHECK_EQUAL(clearH2Connections(), 0U);
+}
+
+BOOST_AUTO_TEST_SUITE_END();
+#endif /* HAVE_NGHTTP2 */
index 884780b28c383db76d3db75187846b9c8c586129..9c7e1be99f4f734dde51d970f209b9618b30d73f 100644 (file)
@@ -1477,7 +1477,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS), threadData, now);
     IncomingTCPConnectionState::handleIO(state, now);
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
-    BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size() * backend->retries);
+    BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size() * backend->d_retries);
     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 */
@@ -1538,7 +1538,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
     IncomingTCPConnectionState::handleIO(state, now);
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), query.size());
     BOOST_CHECK(s_writeBuffer == query);
-    BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size() * backend->retries);
+    BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size() * backend->d_retries);
     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 */
index 083d72ee93ff9bcbe6ece092ee1df8a802d58111..57d3d2ccbf2526eaf376cc7f8282af3779c9b6b6 100644 (file)
@@ -486,18 +486,25 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
 
     @classmethod
     def recvTCPResponseOverConnection(cls, sock, useQueue=False, timeout=2.0):
+        print("reading data")
         message = None
         data = sock.recv(2)
         if data:
             (datalen,) = struct.unpack("!H", data)
+            print(datalen)
             data = sock.recv(datalen)
             if data:
+                print(data)
                 message = dns.message.from_wire(data)
 
+        print(useQueue)
         if useQueue and not cls._fromResponderQueue.empty():
             receivedQuery = cls._fromResponderQueue.get(True, timeout)
+            print("Got from queue")
+            print(receivedQuery)
             return (receivedQuery, message)
         else:
+            print("queue empty")
             return message
 
     @classmethod
@@ -519,8 +526,13 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
             sock.close()
 
         receivedQuery = None
+        print(useQueue)
         if useQueue and not cls._fromResponderQueue.empty():
+            print("Got from queue")
+            print(receivedQuery)
             receivedQuery = cls._fromResponderQueue.get(True, timeout)
+        else:
+          print("queue is empty")
 
         return (receivedQuery, message)
 
index 28a579c27feb76f65438829d8e617d110f83d9b1..205518c1bbf2f91c70e4c264c311ae0552d01890 100644 (file)
@@ -140,7 +140,6 @@ class OutgoingDOHBrokenResponsesTests(object):
         query = dns.message.make_query(name, 'A', 'IN')
 
         (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
-        print(receivedResponse)
         self.assertEqual(receivedResponse, None)
 
         name = 'invalid-dns-payload.broken-responses.outgoing-doh.test.powerdns.com.'
@@ -155,6 +154,48 @@ class OutgoingDOHBrokenResponsesTests(object):
         (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
         self.assertEqual(receivedResponse, None)
 
+        # but a valid response should be successful
+        name = 'valid.broken-responses.outgoing-doh.test.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+
+        (_, receivedResponse) = self.sendUDPQuery(query, response)
+        # we can't check the received query because the responder does not populate the queue..
+        # self.assertEqual(query, receivedQuery)
+        self.assertEqual(response, receivedResponse)
+
+    def testTCP(self):
+        """
+        Outgoing DOH (broken responses): TCP query is sent via DOH
+        """
+        name = '500-status.broken-responses.outgoing-doh.test.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+        self.assertEqual(receivedResponse, None)
+
+        name = 'invalid-dns-payload.broken-responses.outgoing-doh.test.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+        self.assertEqual(receivedResponse, None)
+
+        name = 'closing-connection-id.broken-responses.outgoing-doh.test.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+        self.assertEqual(receivedResponse, None)
+
+        # but a valid response should be successful
+        name = 'valid.broken-responses.outgoing-doh.test.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+
+        (_, receivedResponse) = self.sendTCPQuery(query, response)
+        # we can't check the received query because the responder does not populate the queue..
+        #self.assertEqual(query, receivedQuery)
+        self.assertEqual(response, receivedResponse)
+
 class TestOutgoingDOHOpenSSL(DNSDistTest, OutgoingDOHTests):
     _tlsBackendPort = 10543
     _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
@@ -322,6 +363,7 @@ class TestOutgoingDOHBrokenResponsesGnuTLS(DNSDistTest, OutgoingDOHBrokenRespons
     webserver("127.0.0.1:%s")
     setWebserverConfig({password="%s", apiKey="%s"})
     """
+    _verboseMode = True
 
     def callback(request):