]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Add a lot more of TCP unit tests
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 11 Feb 2021 18:04:37 +0000 (19:04 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 2 Mar 2021 10:39:48 +0000 (11:39 +0100)
pdns/dnsdistdist/test-dnsdisttcp_cc.cc

index e725a56d97ff2c93c83d2aaf7c92da1ab843d294..77bad0e33e65401c9110eaad73e206c5f7431e85 100644 (file)
@@ -41,27 +41,16 @@ GlobalStateHolder<servers_t> g_dstates;
 
 QueryCount g_qcount;
 
-
 bool checkDNSCryptQuery(const ClientState& cs, PacketBuffer& query, std::shared_ptr<DNSCryptQuery>& dnsCryptQuery, time_t now, bool tcp)
 {
   return false;
 }
 
-bool processResponse(PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, bool muted)
-{
-  return false;
-}
-
 bool checkQueryHeaders(const struct dnsheader* dh)
 {
   return true;
 }
 
-bool responseContentMatches(const PacketBuffer& response, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const ComboAddress& remote, unsigned int& qnameWireLength)
-{
-  return true;
-}
-
 uint64_t uptimeOfProcess(const std::string& str)
 {
   return 0;
@@ -83,6 +72,28 @@ ProcessQueryResult processQuery(DNSQuestion& dq, ClientState& cs, LocalHolders&
   return ProcessQueryResult::Drop;
 }
 
+static std::function<bool(const PacketBuffer& response, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const ComboAddress& remote, unsigned int& qnameWireLength)> s_responseContentMatches;
+
+bool responseContentMatches(const PacketBuffer& response, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const ComboAddress& remote, unsigned int& qnameWireLength)
+{
+  if (s_responseContentMatches) {
+    return s_responseContentMatches(response, qname, qtype, qclass, remote, qnameWireLength);
+  }
+
+  return true;
+}
+
+static std::function<bool(PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, bool muted)> s_processResponse;
+
+bool processResponse(PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, bool muted)
+{
+  if (s_processResponse) {
+    return s_processResponse(response, localRespRulactions, dr, muted);
+  }
+
+  return false;
+}
+
 BOOST_AUTO_TEST_SUITE(test_dnsdisttcp_cc)
 
 struct ExpectedStep
@@ -90,14 +101,11 @@ struct ExpectedStep
 public:
   enum class ExpectedRequest { handshake, connect, read, write, close };
 
-  ExpectedStep(ExpectedRequest r, IOState n): ExpectedStep(r, n, 0)
-  {
-  }
-
-  ExpectedStep(ExpectedRequest r, IOState n, size_t b): request(r), nextState(n), bytes(b)
+  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};
@@ -112,8 +120,10 @@ static ExpectedStep getStep()
   return res;
 }
 
-static boost::optional<PacketBuffer> s_readBuffer;
+static PacketBuffer s_readBuffer;
 static PacketBuffer s_writeBuffer;
+static PacketBuffer s_backendReadBuffer;
+static PacketBuffer s_backendWriteBuffer;
 
 std::ostream& operator<<(std::ostream &os, const ExpectedStep::ExpectedRequest d);
 
@@ -126,14 +136,18 @@ std::ostream& operator<<(std::ostream &os, const ExpectedStep::ExpectedRequest d
 
 class MockupTLSConnection : public TLSConnection
 {
-private:
 public:
+  MockupTLSConnection(int descriptor, bool client = false): d_descriptor(descriptor), d_client(client)
+  {
+  }
+
   ~MockupTLSConnection() { }
 
   IOState tryHandshake() override
   {
     auto step = getStep();
     BOOST_REQUIRE_EQUAL(step.request, ExpectedStep::ExpectedRequest::handshake);
+
     return step.nextState;
   }
 
@@ -147,6 +161,9 @@ public:
     BOOST_REQUIRE_EQUAL(step.request, ExpectedStep::ExpectedRequest::write);
 
     if (step.bytes == 0) {
+      if (step.nextState == IOState::NeedWrite) {
+        return step.nextState;
+      }
       throw std::runtime_error("Remote host closed the connection");
     }
 
@@ -157,7 +174,8 @@ public:
       toWrite = step.bytes;
     }
 
-    s_writeBuffer.insert(s_writeBuffer.end(), buffer.begin() + pos, buffer.begin() + pos + toWrite);
+    auto& externalBuffer = d_client ? s_backendWriteBuffer : s_writeBuffer;
+    externalBuffer.insert(externalBuffer.end(), buffer.begin() + pos, buffer.begin() + pos + toWrite);
     pos += toWrite;
 
     return step.nextState;
@@ -173,21 +191,35 @@ public:
     BOOST_REQUIRE_EQUAL(step.request, ExpectedStep::ExpectedRequest::read);
 
     if (step.bytes == 0) {
+      if (step.nextState == IOState::NeedRead) {
+        return step.nextState;
+      }
       throw std::runtime_error("Remote host closed the connection");
     }
 
-    if (s_readBuffer) {
-      toRead -= pos;
+    auto& externalBuffer = d_client ? s_backendReadBuffer : s_readBuffer;
+    toRead -= pos;
 
-      if (step.bytes < toRead) {
-        toRead = step.bytes;
-      }
-      BOOST_REQUIRE_GE(buffer.size(), toRead);
-      BOOST_REQUIRE_GE(s_readBuffer->size(), toRead);
+    if (step.bytes < toRead) {
+      toRead = step.bytes;
+    }
+    BOOST_REQUIRE_GE(buffer.size(), toRead);
+    BOOST_REQUIRE_GE(externalBuffer.size(), toRead);
+
+    std::copy(externalBuffer.begin(), externalBuffer.begin() + toRead, buffer.begin() + pos);
+    pos += toRead;
+    externalBuffer.erase(externalBuffer.begin(), externalBuffer.begin() + toRead);
 
-      std::copy(s_readBuffer->begin(), s_readBuffer->begin() + toRead, buffer.begin() + pos);
-      pos += toRead;
-      s_readBuffer->erase(s_readBuffer->begin(), s_readBuffer->begin() + toRead);
+    return step.nextState;
+  }
+
+  IOState tryConnect(bool fastOpen, const ComboAddress& remote) override
+  {
+    auto step = getStep();
+    BOOST_REQUIRE_EQUAL(step.request, ExpectedStep::ExpectedRequest::connect);
+
+    if (step.cb) {
+      step.cb(d_descriptor, step);
     }
 
     return step.nextState;
@@ -228,11 +260,6 @@ public:
   {
   }
 
-  IOState tryConnect(bool fastOpen, const ComboAddress& remote) override
-  {
-    return IOState::Done;
-  }
-
   size_t read(void* buffer, size_t bufferSize, unsigned int readTimeout, unsigned int totalTimeout=0) override
   {
     return 0;
@@ -242,6 +269,9 @@ public:
   {
     return 0;
   }
+private:
+  const int d_descriptor;
+  bool d_client{false};
 };
 
 class MockupTLSCtx : public TLSCtx
@@ -253,21 +283,21 @@ public:
 
   std::unique_ptr<TLSConnection> getConnection(int socket, unsigned int timeout, time_t now) override
   {
-    return std::make_unique<MockupTLSConnection>();
+    return std::make_unique<MockupTLSConnection>(socket);
   }
 
-  void rotateTicketsKey(time_t now) override
+  std::unique_ptr<TLSConnection> getClientConnection(const std::string& host, int socket, unsigned int timeout) override
   {
+    return std::make_unique<MockupTLSConnection>(socket, true);
   }
 
-  size_t getTicketsKeysCount() override
+  void rotateTicketsKey(time_t now) override
   {
-    return 0;
   }
 
-  std::unique_ptr<TLSConnection> getClientConnection(const std::string& host, int socket, unsigned int timeout) override
+  size_t getTicketsKeysCount() override
   {
-    return nullptr;
+    return 0;
   }
 };
 
@@ -349,8 +379,6 @@ private:
   std::set<int> ready;
 };
 
-#warning TODO: outgoing proxy protocol, out-of-order query from cache while pending response (short write) from backend, exception while processing the response
-
 BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
 {
   ComboAddress local("192.0.2.1:80");
@@ -372,10 +400,11 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
   const uint8_t sizeBytes[] = { static_cast<uint8_t>(querySize / 256), static_cast<uint8_t>(querySize % 256) };
   query.insert(query.begin(), sizeBytes, sizeBytes + 2);
 
-  g_verbose = true;
+  g_proxyProtocolACL.clear();
 
   {
     /* drop right away */
+    cerr<<"=> drop right away"<<endl;
     s_readBuffer = query;
     s_writeBuffer.clear();
     s_steps = {
@@ -390,11 +419,12 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS), threadData, now);
     IncomingTCPConnectionState::handleIO(state, now);
-    BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0);
+    BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
   }
 
   {
     /* self-generated REFUSED, client closes connection right away */
+    cerr<<"=> self-gen"<<endl;
     s_readBuffer = query;
     s_writeBuffer.clear();
     s_steps = {
@@ -417,6 +447,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
   }
 
   {
+    cerr<<"=> shorts"<<endl;
     /* need write then read during handshake,
        short read on the size, then on the query itself,
        self-generated REFUSED, short write on the response,
@@ -454,6 +485,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
   }
 
   {
+    cerr<<"=> exception while handling the query"<<endl;
     /* Exception raised while handling the query */
     s_readBuffer = query;
     s_writeBuffer.clear();
@@ -469,19 +501,21 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS), threadData, now);
     IncomingTCPConnectionState::handleIO(state, now);
-    BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0);
+    BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
   }
 
   {
 #if 0
+    cerr<<"=> 10k self-generated pipelined on the same connection"<<endl;
+
     /* 10k self-generated REFUSED pipelined on the same connection */
     size_t count = 10000;
-    s_readBuffer->clear();
+    s_readBuffer.clear();
     s_writeBuffer.clear();
     s_steps = { { ExpectedStep::ExpectedRequest::handshake, IOState::Done } };
 
     for (size_t idx = 0; idx < count; idx++) {
-      s_readBuffer->insert(s_readBuffer->end(), query.begin(), query.end());
+      s_readBuffer.insert(s_readBuffer.end(), query.begin(), query.end());
       s_steps.push_back({ ExpectedStep::ExpectedRequest::read, IOState::Done, 2 });
       s_steps.push_back({ ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 });
       s_steps.push_back({ ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() + 2 });
@@ -502,6 +536,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
   }
 
   {
+    cerr<<"=> timeout while reading the query"<<endl;
     /* timeout while reading the query */
     s_readBuffer = query;
     s_writeBuffer.clear();
@@ -522,7 +557,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS), threadData, now);
     IncomingTCPConnectionState::handleIO(state, now);
-    BOOST_CHECK_EQUAL(threadData.mplexer->run(&now), 0);
+    BOOST_CHECK_EQUAL(threadData.mplexer->run(&now), 0U);
     struct timeval later = now;
     later.tv_sec += g_tcpRecvTimeout + 1;
     auto expiredReadConns = threadData.mplexer->getTimeouts(later, false);
@@ -534,10 +569,11 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
         cbState->handleTimeout(cbState, false);
       }
     }
-    BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0);
+    BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
   }
 
   {
+    cerr<<"=> timeout while writing the response"<<endl;
     /* timeout while writing the response */
     s_readBuffer = query;
     s_writeBuffer.clear();
@@ -557,7 +593,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS), threadData, now);
     IncomingTCPConnectionState::handleIO(state, now);
-    BOOST_CHECK_EQUAL(threadData.mplexer->run(&now), 0);
+    BOOST_CHECK_EQUAL(threadData.mplexer->run(&now), 0U);
     struct timeval later = now;
     later.tv_sec += g_tcpRecvTimeout + 1;
     auto expiredWriteConns = threadData.mplexer->getTimeouts(later, true);
@@ -569,21 +605,47 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
         cbState->handleTimeout(cbState, false);
       }
     }
-    BOOST_CHECK_EQUAL(s_writeBuffer.size(), 1);
+    BOOST_CHECK_EQUAL(s_writeBuffer.size(), 1U);
   }
 
+}
+
+BOOST_AUTO_TEST_CASE(test_IncomingConnectionWithProxyProtocol_SelfAnswered)
+{
+  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);
+
+  TCPClientThreadData threadData;
+  threadData.mplexer = std::make_unique<MockupFDMultiplexer>();
+
+  struct timeval now;
+  gettimeofday(&now, nullptr);
+
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> pwQ(query, DNSName("powerdns.com."), QType::A, QClass::IN, 0);
+  pwQ.getHeader()->rd = 1;
+
+  uint16_t querySize = static_cast<uint16_t>(query.size());
+  const uint8_t sizeBytes[] = { static_cast<uint8_t>(querySize / 256), static_cast<uint8_t>(querySize % 256) };
+  query.insert(query.begin(), sizeBytes, sizeBytes + 2);
+
+  g_proxyProtocolACL.clear();
+  g_proxyProtocolACL.addMask("0.0.0.0/0");
+
   {
+    cerr<<"=> reading PP"<<endl;
     /* reading a proxy protocol payload */
     auto proxyPayload = makeProxyHeader(true, ComboAddress("192.0.2.1"), ComboAddress("192.0.2.2"), {});
     BOOST_REQUIRE_GT(proxyPayload.size(), s_proxyProtocolMinimumHeaderSize);
     s_readBuffer = query;
     // preprend the proxy protocol payload
-    s_readBuffer->insert(s_readBuffer->begin(), proxyPayload.begin(), proxyPayload.end());
+    s_readBuffer.insert(s_readBuffer.begin(), proxyPayload.begin(), proxyPayload.end());
     // append a second query
-    s_readBuffer->insert(s_readBuffer->end(), query.begin(), query.end());
+    s_readBuffer.insert(s_readBuffer.end(), query.begin(), query.end());
     s_writeBuffer.clear();
-    g_proxyProtocolACL.clear();
-    g_proxyProtocolACL.addMask("0.0.0.0/0");
+
     s_steps = {
       { ExpectedStep::ExpectedRequest::handshake, IOState::Done },
       { ExpectedStep::ExpectedRequest::read, IOState::Done, s_proxyProtocolMinimumHeaderSize },
@@ -606,22 +668,48 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS), threadData, now);
     IncomingTCPConnectionState::handleIO(state, now);
-    BOOST_CHECK_EQUAL(threadData.mplexer->run(&now), 0);
+    BOOST_CHECK_EQUAL(threadData.mplexer->run(&now), 0U);
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), query.size() * 2U);
   }
 
   {
+    cerr<<"=> Invalid PP"<<endl;
+    /* reading a (broken) proxy protocol payload */
+    auto proxyPayload = std::vector<uint8_t>(s_proxyProtocolMinimumHeaderSize);
+    std::fill(proxyPayload.begin(), proxyPayload.end(), 0);
+
+    s_readBuffer = query;
+    // preprend the proxy protocol payload
+    s_readBuffer.insert(s_readBuffer.begin(), proxyPayload.begin(), proxyPayload.end());
+    s_writeBuffer.clear();
+
+    s_steps = {
+      { ExpectedStep::ExpectedRequest::handshake, IOState::Done },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, s_proxyProtocolMinimumHeaderSize },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+    };
+    s_processQuery = [](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+      return ProcessQueryResult::SendAnswer;
+    };
+
+    auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS), threadData, now);
+    IncomingTCPConnectionState::handleIO(state, now);
+
+    BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
+  }
+
+  {
+    cerr<<"=> timeout while reading PP"<<endl;
     /* timeout while reading the proxy protocol payload */
     auto proxyPayload = makeProxyHeader(true, ComboAddress("192.0.2.1"), ComboAddress("192.0.2.2"), {});
     BOOST_REQUIRE_GT(proxyPayload.size(), s_proxyProtocolMinimumHeaderSize);
     s_readBuffer = query;
     // preprend the proxy protocol payload
-    s_readBuffer->insert(s_readBuffer->begin(), proxyPayload.begin(), proxyPayload.end());
+    s_readBuffer.insert(s_readBuffer.begin(), proxyPayload.begin(), proxyPayload.end());
     // append a second query
-    s_readBuffer->insert(s_readBuffer->end(), query.begin(), query.end());
+    s_readBuffer.insert(s_readBuffer.end(), query.begin(), query.end());
     s_writeBuffer.clear();
-    g_proxyProtocolACL.clear();
-    g_proxyProtocolACL.addMask("0.0.0.0/0");
+
     s_steps = {
       { ExpectedStep::ExpectedRequest::handshake, IOState::Done },
       { ExpectedStep::ExpectedRequest::read, IOState::Done, s_proxyProtocolMinimumHeaderSize },
@@ -637,7 +725,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS), threadData, now);
     IncomingTCPConnectionState::handleIO(state, now);
-    BOOST_CHECK_EQUAL(threadData.mplexer->run(&now), 0);
+    BOOST_CHECK_EQUAL(threadData.mplexer->run(&now), 0U);
     struct timeval later = now;
     later.tv_sec += g_tcpRecvTimeout + 1;
     auto expiredReadConns = threadData.mplexer->getTimeouts(later, false);
@@ -649,16 +737,846 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
         cbState->handleTimeout(cbState, false);
       }
     }
-    BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0);
+    BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
   }
-
 }
 
-BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendAnswersRightAway)
+BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
 {
-//int sockets[2];
-//int res = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets);
-//BOOST_REQUIRE_EQUAL(res, 0);
-}
+  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);
+
+  TCPClientThreadData threadData;
+  threadData.mplexer = std::make_unique<MockupFDMultiplexer>();
+
+  struct timeval now;
+  gettimeofday(&now, nullptr);
+
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> pwQ(query, DNSName("powerdns.com."), QType::A, QClass::IN, 0);
+  pwQ.getHeader()->rd = 1;
+
+  uint16_t querySize = static_cast<uint16_t>(query.size());
+  const uint8_t sizeBytes[] = { static_cast<uint8_t>(querySize / 256), static_cast<uint8_t>(querySize % 256) };
+  query.insert(query.begin(), sizeBytes, sizeBytes + 2);
+
+  g_verbose = true;
+
+  g_proxyProtocolACL.clear();
+
+  {
+    /* pass to backend, backend answers right away, client closes the connection */
+    cerr<<"=> Query to backend, backend answers right away"<<endl;
+    s_readBuffer = query;
+    s_writeBuffer.clear();
+
+    s_backendReadBuffer = query;
+    s_backendWriteBuffer.clear();
+
+    s_steps = {
+      { ExpectedStep::ExpectedRequest::handshake, IOState::Done },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      /* opening a connection to the backend */
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 0 },
+      /* closing client connection */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      /* closing a connection to the backend */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+    };
+    s_processQuery = [tlsCtx](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+
+      selectedBackend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+      selectedBackend->d_tlsCtx = tlsCtx;
+      return ProcessQueryResult::PassToBackend;
+    };
+    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, bool muted) -> bool {
+      return true;
+    };
+
+    auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS), threadData, now);
+    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());
+    BOOST_CHECK(s_backendWriteBuffer == query);
+    /* we need to clear them now, otherwise we end up with dangling pointers to the steps via the TLS context, etc */
+    IncomingTCPConnectionState::clearAllDownstreamConnections();
+  }
+
+  {
+    /* pass to backend, backend answers right away, exception while handling the response */
+    cerr<<"=> Exception while handling the response sent by the backend"<<endl;
+    s_readBuffer = query;
+    s_writeBuffer.clear();
+
+    s_backendReadBuffer = query;
+    s_backendWriteBuffer.clear();
+
+    s_steps = {
+      { ExpectedStep::ExpectedRequest::handshake, IOState::Done },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      /* opening a connection to the backend */
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      /* closing client connection */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      /* closing a connection to the backend */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+    };
+    s_processQuery = [tlsCtx](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+
+      selectedBackend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+      selectedBackend->d_tlsCtx = tlsCtx;
+      return ProcessQueryResult::PassToBackend;
+    };
+    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, bool muted) -> bool {
+      throw std::runtime_error("Unexpected error while processing the response");
+    };
+
+    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());
+    BOOST_CHECK(s_backendWriteBuffer == query);
+    /* we need to clear them now, otherwise we end up with dangling pointers to the steps via the TLS context, etc */
+    IncomingTCPConnectionState::clearAllDownstreamConnections();
+  }
+
+  {
+    /* pass to backend, backend answers right away, processResponse() fails */
+    cerr<<"=> Response processing fails "<<endl;
+    s_readBuffer = query;
+    s_writeBuffer.clear();
+
+    s_backendReadBuffer = query;
+    s_backendWriteBuffer.clear();
+
+    s_steps = {
+      { ExpectedStep::ExpectedRequest::handshake, IOState::Done },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      /* opening a connection to the backend */
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      /* closing client connection */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      /* closing a connection to the backend */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+    };
+    s_processQuery = [tlsCtx](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+
+      selectedBackend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+      selectedBackend->d_tlsCtx = tlsCtx;
+      return ProcessQueryResult::PassToBackend;
+    };
+    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, bool muted) -> bool {
+      return false;
+    };
+
+    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());
+    BOOST_CHECK(s_backendWriteBuffer == query);
+    /* we need to clear them now, otherwise we end up with dangling pointers to the steps via the TLS context, etc */
+    IncomingTCPConnectionState::clearAllDownstreamConnections();
+  }
+
+  {
+    /* pass to backend, backend answers right away, ID matching fails */
+    cerr<<"=> ID matching fails "<<endl;
+    s_readBuffer = query;
+    s_writeBuffer.clear();
+
+    auto response = query;
+    /* mess with the transaction ID */
+    response.at(3) ^= 42;
+
+    s_backendReadBuffer = response;
+    s_backendWriteBuffer.clear();
+
+    s_steps = {
+      { ExpectedStep::ExpectedRequest::handshake, IOState::Done },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      /* opening a connection to the backend */
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      /* closing client connection */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      /* closing a connection to the backend */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+    };
+    s_processQuery = [tlsCtx](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+
+      selectedBackend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+      selectedBackend->d_tlsCtx = tlsCtx;
+      return ProcessQueryResult::PassToBackend;
+    };
+    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, bool muted) -> bool {
+      return true;
+    };
+
+    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());
+    BOOST_CHECK(s_backendWriteBuffer == query);
+    /* we need to clear them now, otherwise we end up with dangling pointers to the steps via the TLS context, etc */
+    IncomingTCPConnectionState::clearAllDownstreamConnections();
+  }
+
+  {
+    /* connect in progress, short write to the backend, short read from the backend, client */
+    cerr<<"=> Short read and write to backend"<<endl;
+    s_readBuffer = query;
+    // append a second query
+    s_readBuffer.insert(s_readBuffer.end(), query.begin(), query.end());
+    s_writeBuffer.clear();
+
+    s_backendReadBuffer = query;
+    // append a second query
+    s_backendReadBuffer.insert(s_backendReadBuffer.end(), query.begin(), query.end());
+    s_backendWriteBuffer.clear();
+
+    s_steps = {
+      { ExpectedStep::ExpectedRequest::handshake, IOState::Done },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      /* connect to backend */
+      { ExpectedStep::ExpectedRequest::connect, IOState::NeedWrite, 0, [&threadData](int desc, const ExpectedStep& step) {
+          /* set the outgoing descriptor (backend connection) as ready */
+          dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setReady(desc);
+        }
+      },
+      /* send query */
+      { ExpectedStep::ExpectedRequest::write, IOState::NeedWrite, 1 },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() - 1 },
+      /* read response */
+      { ExpectedStep::ExpectedRequest::read, IOState::NeedRead, 1 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 1 },
+      { ExpectedStep::ExpectedRequest::read, IOState::NeedRead, query.size() - 3 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 1 },
+      /* write response to client */
+      { ExpectedStep::ExpectedRequest::write, IOState::NeedWrite, query.size() - 1 },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, 1 },
+      /* read second query */
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      /* write second query to backend */
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      /* read second response */
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      /* write second response */
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      /* read from client */
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 0 },
+      /* close connection to client */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      /* close connection to the backend, eventually */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+    };
+
+    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;
+
+    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+      selectedBackend = backend;
+      return ProcessQueryResult::PassToBackend;
+    };
+    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, bool muted) -> bool {
+      return true;
+    };
+
+    /* set the incoming descriptor as ready! */
+    dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setReady(-1);
+    auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS), threadData, now);
+    IncomingTCPConnectionState::handleIO(state, now);
+    while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
+      threadData.mplexer->run(&now);
+    }
+    BOOST_CHECK_EQUAL(s_writeBuffer.size(), query.size() * 2U);
+    BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size() * 2U);
+    /* we need to clear them now, otherwise we end up with dangling pointers to the steps via the TLS context, etc */
+    IncomingTCPConnectionState::clearAllDownstreamConnections();
+  }
+
+  {
+    /* connection refused by the backend */
+    cerr<<"=> Connection refused by the backend "<<endl;
+    s_readBuffer = query;
+    s_writeBuffer.clear();
+
+    s_backendReadBuffer.clear();
+    s_backendWriteBuffer.clear();
+
+    s_steps = {
+      { ExpectedStep::ExpectedRequest::handshake, IOState::Done },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      /* opening a connection to the backend (5 tries by default) */
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done, 0, [](int descriptor, const ExpectedStep& step) {
+          throw NetworkError("Connection refused by the backend");
+        }
+      },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done, 0, [](int descriptor, const ExpectedStep& step) {
+          throw NetworkError("Connection refused by the backend");
+        }
+      },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done, 0, [](int descriptor, const ExpectedStep& step) {
+          throw NetworkError("Connection refused by the backend");
+        }
+      },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done, 0, [](int descriptor, const ExpectedStep& step) {
+          throw NetworkError("Connection refused by the backend");
+        }
+      },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done, 0, [](int descriptor, const ExpectedStep& step) {
+          throw NetworkError("Connection refused by the backend");
+        }
+      },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      /* closing client connection */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+    };
+    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;
+
+    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+
+      selectedBackend = backend;
+      return ProcessQueryResult::PassToBackend;
+    };
+    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, bool muted) -> bool {
+      return true;
+    };
+
+    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(), 0U);
+    /* we need to clear them now, otherwise we end up with dangling pointers to the steps via the TLS context, etc */
+    IncomingTCPConnectionState::clearAllDownstreamConnections();
+  }
+
+  {
+    /* timeout from the backend (write) */
+    cerr<<"=> Timeout from the backend (write) "<<endl;
+    s_readBuffer = query;
+    s_writeBuffer.clear();
+
+    s_backendReadBuffer.clear();
+    s_backendWriteBuffer.clear();
+
+    s_steps = {
+      { ExpectedStep::ExpectedRequest::handshake, IOState::Done },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      /* opening a connection to the backend (retrying 5 times) */
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::NeedWrite },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      /* closing client connection */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+    };
+    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;
+
+    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+
+      selectedBackend = backend;
+      return ProcessQueryResult::PassToBackend;
+    };
+    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, bool muted) -> bool {
+      return true;
+    };
+
+    auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS), threadData, now);
+    IncomingTCPConnectionState::handleIO(state, now);
+    struct timeval later = now;
+    later.tv_sec += backend->tcpSendTimeout + 1;
+    auto expiredWriteConns = threadData.mplexer->getTimeouts(later, true);
+    BOOST_CHECK_EQUAL(expiredWriteConns.size(), 1U);
+    for (const auto& cbData : expiredWriteConns) {
+      if (cbData.second.type() == typeid(std::shared_ptr<TCPConnectionToBackend>)) {
+        auto cbState = boost::any_cast<std::shared_ptr<TCPConnectionToBackend>>(cbData.second);
+        cbState->handleTimeout(later, true);
+      }
+    }
+    BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
+    BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), 0U);
+    /* we need to clear them now, otherwise we end up with dangling pointers to the steps via the TLS context, etc */
+    IncomingTCPConnectionState::clearAllDownstreamConnections();
+  }
+
+  {
+    /* timeout from the backend (read) */
+    cerr<<"=> Timeout from the backend (read) "<<endl;
+    s_readBuffer = query;
+    s_writeBuffer.clear();
+
+    s_backendReadBuffer.clear();
+    s_backendWriteBuffer.clear();
+
+    s_steps = {
+      { ExpectedStep::ExpectedRequest::handshake, IOState::Done },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      /* opening a connection to the backend */
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      { ExpectedStep::ExpectedRequest::read, IOState::NeedRead, 0 },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      /* closing client connection */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+    };
+    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;
+
+    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+
+      selectedBackend = backend;
+      return ProcessQueryResult::PassToBackend;
+    };
+    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, bool muted) -> bool {
+      return true;
+    };
+
+    auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS), threadData, now);
+    IncomingTCPConnectionState::handleIO(state, now);
+    struct timeval later = now;
+    later.tv_sec += backend->tcpRecvTimeout + 1;
+    auto expiredConns = threadData.mplexer->getTimeouts(later, false);
+    BOOST_CHECK_EQUAL(expiredConns.size(), 1U);
+    for (const auto& cbData : expiredConns) {
+      if (cbData.second.type() == typeid(std::shared_ptr<TCPConnectionToBackend>)) {
+        auto cbState = boost::any_cast<std::shared_ptr<TCPConnectionToBackend>>(cbData.second);
+        cbState->handleTimeout(later, false);
+      }
+    }
+    BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
+    BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size());
+    /* we need to clear them now, otherwise we end up with dangling pointers to the steps via the TLS context, etc */
+    IncomingTCPConnectionState::clearAllDownstreamConnections();
+  }
+
+  {
+    /* connection closed from the backend (write) */
+    cerr<<"=> Connection closed from the backend (write) "<<endl;
+    s_readBuffer = query;
+    s_writeBuffer.clear();
+
+    s_backendReadBuffer.clear();
+    s_backendWriteBuffer.clear();
+
+    s_steps = {
+      { ExpectedStep::ExpectedRequest::handshake, IOState::Done },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      /* opening a connection to the backend, connection closed on first write (5 attempts) */
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, 0 },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, 0 },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, 0 },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, 0 },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, 0 },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      /* closing client connection */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+    };
+    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;
+
+    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+
+      selectedBackend = backend;
+      return ProcessQueryResult::PassToBackend;
+    };
+    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, bool muted) -> bool {
+      return true;
+    };
+
+    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(), 0U);
+    /* we need to clear them now, otherwise we end up with dangling pointers to the steps via the TLS context, etc */
+    IncomingTCPConnectionState::clearAllDownstreamConnections();
+  }
+
+  {
+    /* connection closed from the backend (write) 4 times then succeeds */
+    cerr<<"=> Connection closed from the backend (write) 4 times then succeeds"<<endl;
+    s_readBuffer = query;
+    s_writeBuffer.clear();
+
+    s_backendReadBuffer = query;
+    s_backendWriteBuffer.clear();
+
+    s_steps = {
+      { ExpectedStep::ExpectedRequest::handshake, IOState::Done },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      /* opening a connection to the backend, connection closed on first write (5 attempts) */
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, 0 },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, 0 },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, 0 },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, 0 },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      /* reading the response */
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      /* send the response to the client */
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      /* client closes the connection */
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 0 },
+      /* closing client connection */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      /* then eventually the backend one */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+    };
+    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;
+
+    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+
+      selectedBackend = backend;
+      return ProcessQueryResult::PassToBackend;
+    };
+    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, bool muted) -> bool {
+      return true;
+    };
+
+    auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS), threadData, now);
+    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());
+    /* we need to clear them now, otherwise we end up with dangling pointers to the steps via the TLS context, etc */
+    IncomingTCPConnectionState::clearAllDownstreamConnections();
+  }
+
+  {
+    /* connection closed from the backend (read) */
+    cerr<<"=> Connection closed from the backend (read) "<<endl;
+    s_readBuffer = query;
+    s_writeBuffer.clear();
+
+    s_backendReadBuffer.clear();
+    s_backendWriteBuffer.clear();
+
+    s_steps = {
+      { ExpectedStep::ExpectedRequest::handshake, IOState::Done },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      /* opening a connection to the backend, connection closed on read, 5 attempts, last one succeeds */
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 0 },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 0 },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 0 },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 0 },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 0 },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      /* closing client connection */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+    };
+    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;
+
+    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+
+      selectedBackend = backend;
+      return ProcessQueryResult::PassToBackend;
+    };
+    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, bool muted) -> bool {
+      return true;
+    };
+
+    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);
+    /* we need to clear them now, otherwise we end up with dangling pointers to the steps via the TLS context, etc */
+    IncomingTCPConnectionState::clearAllDownstreamConnections();
+  }
+
+  {
+    /* connection closed from the backend (read) 4 times then succeeds */
+    cerr<<"=> Connection closed from the backend (read) 4 times then succeeds "<<endl;
+    s_readBuffer = query;
+    s_writeBuffer.clear();
+
+    s_backendReadBuffer = query;
+    s_backendWriteBuffer.clear();
+
+    s_steps = {
+      { ExpectedStep::ExpectedRequest::handshake, IOState::Done },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      /* opening a connection to the backend, connection closed on read, 5 attempts, last one succeeds */
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 0 },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 0 },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 0 },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 0 },
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      /* this time it works */
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      /* sending the response to the client */
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      /* client closes the connection */
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 0 },
+      /* closing client connection */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      /* the eventually the backend one */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+    };
+    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;
+
+    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+
+      selectedBackend = backend;
+      return ProcessQueryResult::PassToBackend;
+    };
+    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, bool muted) -> bool {
+      return true;
+    };
+
+    auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS), threadData, now);
+    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);
+    /* we need to clear them now, otherwise we end up with dangling pointers to the steps via the TLS context, etc */
+    IncomingTCPConnectionState::clearAllDownstreamConnections();
+  }
+
+  {
+#if 0
+    /* 101 queries on the same connection, check that the maximum number of queries kicks in */
+    cerr<<"=> 101 queries on the same connection"<<endl;
+
+    g_maxTCPQueriesPerConn = 100;
+
+    size_t count = 101;
+
+    s_readBuffer = query;
+    s_writeBuffer.clear();
+
+    s_backendReadBuffer.clear();
+    s_backendWriteBuffer.clear();
+
+    s_readBuffer.clear();
+    s_writeBuffer.clear();
+
+    for (size_t idx = 0; idx < count; idx++) {
+      s_readBuffer.insert(s_readBuffer.end(), query.begin(), query.end());
+      s_backendReadBuffer.insert(s_backendReadBuffer.end(), query.begin(), query.end());
+    }
+
+    s_steps = { { ExpectedStep::ExpectedRequest::handshake, IOState::Done },
+                { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+                { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+                /* opening a connection to the backend */
+                { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+                { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() + 2 },
+                { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+                { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+                { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() + 2 }
+    };
+
+    for (size_t idx = 0; idx < count - 1; idx++) {
+      /* read a new query */
+      s_steps.push_back({ ExpectedStep::ExpectedRequest::read, IOState::Done, 2 });
+      s_steps.push_back({ ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 });
+      /* pass it to the backend */
+      s_steps.push_back({ ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() + 2 });
+      s_steps.push_back({ ExpectedStep::ExpectedRequest::read, IOState::Done, 2 });
+      s_steps.push_back({ ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 });
+      /* send the response */
+      s_steps.push_back({ ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() + 2 });
+    };
+    /* close the connection with the client */
+    s_steps.push_back({ ExpectedStep::ExpectedRequest::close, IOState::Done });
+    /* eventually with the backend as well */
+    s_steps.push_back({ ExpectedStep::ExpectedRequest::close, IOState::Done });
+
+    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;
+
+    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+
+      selectedBackend = backend;
+      return ProcessQueryResult::PassToBackend;
+    };
+    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, bool muted) -> bool {
+      return true;
+    };
+
+    auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS), threadData, now);
+    IncomingTCPConnectionState::handleIO(state, now);
+    BOOST_CHECK_EQUAL(s_writeBuffer.size(), query.size() * count);
+
+    /* we need to clear them now, otherwise we end up with dangling pointers to the steps via the TLS context, etc */
+    IncomingTCPConnectionState::clearAllDownstreamConnections();
+
+    g_maxTCPQueriesPerConn = 0;
+#endif
+  }
+
+}
+
+BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
+{
+  ComboAddress local("192.0.2.1:80");
+  ClientState localCS(local, true, false, false, "", {});
+  /* enable out-of-order on the front side */
+  localCS.d_maxInFlightQueriesPerConn = 65536;
+
+  auto tlsCtx = std::make_shared<MockupTLSCtx>();
+  localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+  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;
+  /* enable out-of-order on the backend side as well */
+  backend->d_maxInFlightQueriesPerConn = 65536;
+
+  TCPClientThreadData threadData;
+  threadData.mplexer = std::make_unique<MockupFDMultiplexer>();
+
+  struct timeval now;
+  gettimeofday(&now, nullptr);
+
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> pwQ(query, DNSName("powerdns.com."), QType::A, QClass::IN, 0);
+  pwQ.getHeader()->rd = 1;
+
+  uint16_t querySize = static_cast<uint16_t>(query.size());
+  const uint8_t sizeBytes[] = { static_cast<uint8_t>(querySize / 256), static_cast<uint8_t>(querySize % 256) };
+  query.insert(query.begin(), sizeBytes, sizeBytes + 2);
+
+  g_verbose = true;
+
+  g_proxyProtocolACL.clear();
+
+  {
+    cerr<<"=> 5 OOOR queries to the backend, backend responds out of order"<<endl;
+    s_readBuffer = query;
+    s_writeBuffer.clear();
+#warning TODOOOOOOOOOO
+    s_backendReadBuffer = query;
+    s_backendWriteBuffer.clear();
+
+    s_steps = {
+      { ExpectedStep::ExpectedRequest::handshake, IOState::Done },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      /* opening a connection to the backend */
+      { ExpectedStep::ExpectedRequest::connect, IOState::Done },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 2 },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, query.size() - 2 },
+      { ExpectedStep::ExpectedRequest::write, IOState::Done, query.size() },
+      { ExpectedStep::ExpectedRequest::read, IOState::Done, 0 },
+      /* closing client connection */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+      /* closing a connection to the backend */
+      { ExpectedStep::ExpectedRequest::close, IOState::Done },
+    };
+
+    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+      selectedBackend = backend;
+      return ProcessQueryResult::PassToBackend;
+    };
+    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, bool muted) -> bool {
+      return true;
+    };
+
+    auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS), threadData, now);
+    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());
+    BOOST_CHECK(s_backendWriteBuffer == query);
+    /* we need to clear them now, otherwise we end up with dangling pointers to the steps via the TLS context, etc */
+    IncomingTCPConnectionState::clearAllDownstreamConnections();
+  }
+}
+
+#warning TODO:
+
+// proxy protocol to backend?
+
+// OOOR: OOOR enabled but packet cache hit
+// OOOR: OOOR enabled but backend answers very fast
+// OOOR: OOOR, get 10 queries before the backend can answer. backend doesn't support OOOR, we should get 10 connections. Check that we do reuse them on two subsequent queries
+// OOOR: OOOR, get 10 queries before the backend can answer. backend does support OOOR, respond out of order we should only have 1 connections. Check that we do reuse them on two subsequent queries
+// OOOR: OOOR, get 10 queries before the backend can answer. backend does support OOOR but only up to 5 conns, respond out of order, we should only have 2 connections. Check that we do reuse one of them on two subsequent queries
+// OOOR: get one query, sent it to the backend, start reading the response, get two new queries during that time, finish getting the first answer, send it, timeout read on the client, get the last two answers and send them
+// out-of-order query from cache while pending response (short write) from backend, exception while processing the response
 
 BOOST_AUTO_TEST_SUITE_END();