From 099696a96a0805c5962498428e27867fff0ceeb9 Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Thu, 11 Feb 2021 19:04:37 +0100 Subject: [PATCH] dnsdist: Add a lot more of TCP unit tests --- pdns/dnsdistdist/test-dnsdisttcp_cc.cc | 1054 ++++++++++++++++++++++-- 1 file changed, 986 insertions(+), 68 deletions(-) diff --git a/pdns/dnsdistdist/test-dnsdisttcp_cc.cc b/pdns/dnsdistdist/test-dnsdisttcp_cc.cc index e725a56d97..77bad0e33e 100644 --- a/pdns/dnsdistdist/test-dnsdisttcp_cc.cc +++ b/pdns/dnsdistdist/test-dnsdisttcp_cc.cc @@ -41,27 +41,16 @@ GlobalStateHolder g_dstates; QueryCount g_qcount; - bool checkDNSCryptQuery(const ClientState& cs, PacketBuffer& query, std::shared_ptr& dnsCryptQuery, time_t now, bool tcp) { return false; } -bool processResponse(PacketBuffer& response, LocalStateHolder >& 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 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 >& localRespRulactions, DNSResponse& dr, bool muted)> s_processResponse; + +bool processResponse(PacketBuffer& response, LocalStateHolder >& 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 fn = nullptr): cb(fn), request(r), nextState(n), bytes(b) { } + std::function cb{nullptr}; ExpectedRequest request; IOState nextState; size_t bytes{0}; @@ -112,8 +120,10 @@ static ExpectedStep getStep() return res; } -static boost::optional 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 getConnection(int socket, unsigned int timeout, time_t now) override { - return std::make_unique(); + return std::make_unique(socket); } - void rotateTicketsKey(time_t now) override + std::unique_ptr getClientConnection(const std::string& host, int socket, unsigned int timeout) override { + return std::make_unique(socket, true); } - size_t getTicketsKeysCount() override + void rotateTicketsKey(time_t now) override { - return 0; } - std::unique_ptr 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 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(querySize / 256), static_cast(querySize % 256) }; query.insert(query.begin(), sizeBytes, sizeBytes + 2); - g_verbose = true; + g_proxyProtocolACL.clear(); { /* drop right away */ + cerr<<"=> drop right away"<(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"< shorts"< exception while handling the query"<(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"<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"<(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"<(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(); + localCS.tlsFrontend = std::make_shared(tlsCtx); + + TCPClientThreadData threadData; + threadData.mplexer = std::make_unique(); + + struct timeval now; + gettimeofday(&now, nullptr); + + PacketBuffer query; + GenericDNSPacketWriter pwQ(query, DNSName("powerdns.com."), QType::A, QClass::IN, 0); + pwQ.getHeader()->rd = 1; + + uint16_t querySize = static_cast(query.size()); + const uint8_t sizeBytes[] = { static_cast(querySize / 256), static_cast(querySize % 256) }; + query.insert(query.begin(), sizeBytes, sizeBytes + 2); + + g_proxyProtocolACL.clear(); + g_proxyProtocolACL.addMask("0.0.0.0/0"); + { + cerr<<"=> reading PP"<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(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"<(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& selectedBackend) -> ProcessQueryResult { + return ProcessQueryResult::SendAnswer; + }; + + auto state = std::make_shared(ConnectionInfo(&localCS), threadData, now); + IncomingTCPConnectionState::handleIO(state, now); + + BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U); + } + + { + cerr<<"=> timeout while reading PP"<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(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(); + localCS.tlsFrontend = std::make_shared(tlsCtx); + + TCPClientThreadData threadData; + threadData.mplexer = std::make_unique(); + + struct timeval now; + gettimeofday(&now, nullptr); + + PacketBuffer query; + GenericDNSPacketWriter pwQ(query, DNSName("powerdns.com."), QType::A, QClass::IN, 0); + pwQ.getHeader()->rd = 1; + + uint16_t querySize = static_cast(query.size()); + const uint8_t sizeBytes[] = { static_cast(querySize / 256), static_cast(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"<& selectedBackend) -> ProcessQueryResult { + + selectedBackend = std::make_shared(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 >& localRespRulactions, DNSResponse& dr, bool muted) -> bool { + return true; + }; + + auto state = std::make_shared(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"<& selectedBackend) -> ProcessQueryResult { + + selectedBackend = std::make_shared(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 >& localRespRulactions, DNSResponse& dr, bool muted) -> bool { + throw std::runtime_error("Unexpected error while processing the response"); + }; + + auto state = std::make_shared(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 "<& selectedBackend) -> ProcessQueryResult { + + selectedBackend = std::make_shared(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 >& localRespRulactions, DNSResponse& dr, bool muted) -> bool { + return false; + }; + + auto state = std::make_shared(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 "<& selectedBackend) -> ProcessQueryResult { + + selectedBackend = std::make_shared(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 >& localRespRulactions, DNSResponse& dr, bool muted) -> bool { + return true; + }; + + auto state = std::make_shared(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"<(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(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& selectedBackend) -> ProcessQueryResult { + selectedBackend = backend; + return ProcessQueryResult::PassToBackend; + }; + s_processResponse = [](PacketBuffer& response, LocalStateHolder >& localRespRulactions, DNSResponse& dr, bool muted) -> bool { + return true; + }; + + /* set the incoming descriptor as ready! */ + dynamic_cast(threadData.mplexer.get())->setReady(-1); + auto state = std::make_shared(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 "<(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& selectedBackend) -> ProcessQueryResult { + + selectedBackend = backend; + return ProcessQueryResult::PassToBackend; + }; + s_processResponse = [](PacketBuffer& response, LocalStateHolder >& localRespRulactions, DNSResponse& dr, bool muted) -> bool { + return true; + }; + + auto state = std::make_shared(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) "<(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& selectedBackend) -> ProcessQueryResult { + + selectedBackend = backend; + return ProcessQueryResult::PassToBackend; + }; + s_processResponse = [](PacketBuffer& response, LocalStateHolder >& localRespRulactions, DNSResponse& dr, bool muted) -> bool { + return true; + }; + + auto state = std::make_shared(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)) { + auto cbState = boost::any_cast>(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) "<(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& selectedBackend) -> ProcessQueryResult { + + selectedBackend = backend; + return ProcessQueryResult::PassToBackend; + }; + s_processResponse = [](PacketBuffer& response, LocalStateHolder >& localRespRulactions, DNSResponse& dr, bool muted) -> bool { + return true; + }; + + auto state = std::make_shared(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)) { + auto cbState = boost::any_cast>(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) "<(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& selectedBackend) -> ProcessQueryResult { + + selectedBackend = backend; + return ProcessQueryResult::PassToBackend; + }; + s_processResponse = [](PacketBuffer& response, LocalStateHolder >& localRespRulactions, DNSResponse& dr, bool muted) -> bool { + return true; + }; + + auto state = std::make_shared(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"<(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& selectedBackend) -> ProcessQueryResult { + + selectedBackend = backend; + return ProcessQueryResult::PassToBackend; + }; + s_processResponse = [](PacketBuffer& response, LocalStateHolder >& localRespRulactions, DNSResponse& dr, bool muted) -> bool { + return true; + }; + + auto state = std::make_shared(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) "<(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& selectedBackend) -> ProcessQueryResult { + + selectedBackend = backend; + return ProcessQueryResult::PassToBackend; + }; + s_processResponse = [](PacketBuffer& response, LocalStateHolder >& localRespRulactions, DNSResponse& dr, bool muted) -> bool { + return true; + }; + + auto state = std::make_shared(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 "<(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& selectedBackend) -> ProcessQueryResult { + + selectedBackend = backend; + return ProcessQueryResult::PassToBackend; + }; + s_processResponse = [](PacketBuffer& response, LocalStateHolder >& localRespRulactions, DNSResponse& dr, bool muted) -> bool { + return true; + }; + + auto state = std::make_shared(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"<(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& selectedBackend) -> ProcessQueryResult { + + selectedBackend = backend; + return ProcessQueryResult::PassToBackend; + }; + s_processResponse = [](PacketBuffer& response, LocalStateHolder >& localRespRulactions, DNSResponse& dr, bool muted) -> bool { + return true; + }; + + auto state = std::make_shared(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(); + localCS.tlsFrontend = std::make_shared(tlsCtx); + + auto backend = std::make_shared(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(); + + struct timeval now; + gettimeofday(&now, nullptr); + + PacketBuffer query; + GenericDNSPacketWriter pwQ(query, DNSName("powerdns.com."), QType::A, QClass::IN, 0); + pwQ.getHeader()->rd = 1; + + uint16_t querySize = static_cast(query.size()); + const uint8_t sizeBytes[] = { static_cast(querySize / 256), static_cast(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"<& selectedBackend) -> ProcessQueryResult { + selectedBackend = backend; + return ProcessQueryResult::PassToBackend; + }; + s_processResponse = [](PacketBuffer& response, LocalStateHolder >& localRespRulactions, DNSResponse& dr, bool muted) -> bool { + return true; + }; + + auto state = std::make_shared(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(); -- 2.47.2