/* * This file is part of PowerDNS or dnsdist. * Copyright -- PowerDNS.COM B.V. and its contributors * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * * In addition, for the avoidance of any doubt, permission is granted to * link this program with OpenSSL and to (re)distribute the binaries * produced as the result of such linking. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #define BOOST_TEST_DYN_LINK #define BOOST_TEST_NO_MAIN #include #include "dnswriter.hh" #include "dnsdist.hh" #include "dnsdist-proxy-protocol.hh" #include "dnsdist-rings.hh" #include "dnsdist-nghttp2.hh" #include "sstuff.hh" #ifdef HAVE_NGHTTP2 #include BOOST_AUTO_TEST_SUITE(test_dnsdistnghttp2_cc) struct ExpectedStep { public: enum class ExpectedRequest { handshakeClient, readFromClient, writeToClient, closeClient, connectToBackend, readFromBackend, writeToBackend, closeBackend }; ExpectedStep(ExpectedRequest r, IOState n, size_t b = 0, std::function fn = nullptr) : cb(fn), request(r), nextState(n), bytes(b) { } std::function cb{nullptr}; ExpectedRequest request; IOState nextState; size_t bytes{0}; }; struct ExpectedData { PacketBuffer d_query; PacketBuffer d_response; }; static std::deque s_steps; static std::map s_responses; std::ostream& operator<<(std::ostream& os, const ExpectedStep::ExpectedRequest d); std::ostream& operator<<(std::ostream& os, const ExpectedStep::ExpectedRequest d) { static const std::vector requests = {"handshake with client", "read from client", "write to client", "close connection to client", "connect to the backend", "read from the backend", "write to the backend", "close connection to backend"}; os << requests.at(static_cast(d)); return os; } class DOHConnection { public: DOHConnection(bool needProxyProtocol) : d_session(std::unique_ptr(nullptr, nghttp2_session_del)), d_needProxyProtocol(needProxyProtocol) { nghttp2_session_callbacks* cbs = nullptr; nghttp2_session_callbacks_new(&cbs); std::unique_ptr callbacks(cbs, nghttp2_session_callbacks_del); cbs = nullptr; nghttp2_session_callbacks_set_send_callback(callbacks.get(), send_callback); nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks.get(), on_frame_recv_callback); nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks.get(), on_data_chunk_recv_callback); nghttp2_session_callbacks_set_on_stream_close_callback(callbacks.get(), on_stream_close_callback); nghttp2_session* sess = nullptr; nghttp2_session_server_new(&sess, callbacks.get(), this); d_session = std::unique_ptr(sess, nghttp2_session_del); nghttp2_settings_entry iv[1] = { {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; nghttp2_submit_settings(d_session.get(), NGHTTP2_FLAG_NONE, iv, sizeof(iv) / sizeof(*iv)); } PacketBuffer d_serverOutBuffer; PacketBuffer d_proxyProtocolBuffer; std::map d_queries; std::map d_responses; std::unique_ptr d_session; /* used to replace the stream ID in outgoing frames. Ugly but the library does not let us test weird cases without that */ std::map d_idMapping; bool d_needProxyProtocol; size_t submitIncoming(const PacketBuffer& data, size_t pos, size_t toWrite) { size_t consumed = 0; if (d_needProxyProtocol) { do { auto bytesRemaining = isProxyHeaderComplete(d_proxyProtocolBuffer); if (bytesRemaining < 0) { size_t toConsume = toWrite > static_cast(-bytesRemaining) ? static_cast(-bytesRemaining) : toWrite; d_proxyProtocolBuffer.insert(d_proxyProtocolBuffer.end(), data.begin() + pos, data.begin() + pos + toConsume); pos += toConsume; toWrite -= toConsume; consumed += toConsume; bytesRemaining = isProxyHeaderComplete(d_proxyProtocolBuffer); if (bytesRemaining > 0) { d_needProxyProtocol = false; } else if (bytesRemaining == 0) { throw("Fatal error while parsing proxy protocol payload"); } } else if (bytesRemaining == 0) { throw("Fatal error while parsing proxy protocol payload"); } if (toWrite == 0) { return consumed; } } while (d_needProxyProtocol && toWrite > 0); } ssize_t readlen = nghttp2_session_mem_recv(d_session.get(), &data.at(pos), toWrite); if (readlen < 0) { throw("Fatal error while submitting: " + std::string(nghttp2_strerror(static_cast(readlen)))); } /* just in case, see if we have anything to send */ int rv = nghttp2_session_send(d_session.get()); if (rv != 0) { throw("Fatal error while sending: " + std::string(nghttp2_strerror(rv))); } return readlen; } void submitResponse(uint32_t streamId, PacketBuffer& data) { const nghttp2_nv hdrs[] = {{(uint8_t*)":status", (uint8_t*)"200", sizeof(":status") - 1, sizeof("200") - 1, NGHTTP2_NV_FLAG_NONE}}; nghttp2_data_provider dataProvider; dataProvider.source.ptr = &data; dataProvider.read_callback = [](nghttp2_session* session, int32_t stream_id, uint8_t* buf, size_t length, uint32_t* data_flags, nghttp2_data_source* source, void* user_data) -> ssize_t { auto buffer = reinterpret_cast(source->ptr); size_t toCopy = 0; if (buffer->size() > 0) { toCopy = length > buffer->size() ? buffer->size() : length; memcpy(buf, &buffer->at(0), toCopy); buffer->erase(buffer->begin(), buffer->begin() + toCopy); } if (buffer->size() == 0) { *data_flags |= NGHTTP2_DATA_FLAG_EOF; } // cerr<<"submitting response data of size "<(user_data); // cerr<<"inserting "<d_serverOutBuffer.size()<d_idMapping.empty() && length > 9) { /* frame type == DATA */ if (data[3] == NGHTTP2_DATA) { uint32_t streamId = 0; memcpy(&streamId, &data[5], sizeof(streamId)); const auto it = conn->d_idMapping.find(ntohl(streamId)); if (it != conn->d_idMapping.end()) { streamId = htonl(it->second); std::vector editedData(length); std::copy(data, data + length, editedData.begin()); memcpy(&editedData.at(5), &streamId, sizeof(streamId)); conn->d_serverOutBuffer.insert(conn->d_serverOutBuffer.end(), editedData.data(), editedData.data() + length); return static_cast(editedData.size()); } } } conn->d_serverOutBuffer.insert(conn->d_serverOutBuffer.end(), data, data + length); return static_cast(length); } static int on_frame_recv_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data) { DOHConnection* conn = reinterpret_cast(user_data); // cerr<<"Frame type is "<hd.type)<hd.type == NGHTTP2_HEADERS || frame->hd.type == NGHTTP2_DATA) && frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { #if 0 auto stream_data = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); /* For DATA and HEADERS frame, this callback may be called after on_stream_close_callback. Check that stream still alive. */ if (stream_data == nullptr) { cerr<<"unable to find stream data!"<d_queries.at(frame->hd.stream_id); BOOST_REQUIRE_GT(query.size(), sizeof(dnsheader)); auto dh = reinterpret_cast(query.data()); uint16_t id = ntohs(dh->id); // cerr<<"got query ID "<(query.data()), query.size(), sizeof(dnsheader), false); if (qname == DNSName("goaway.powerdns.com.")) { conn->submitGoAway(); } else if (qname == DNSName("500.powerdns.com.") && (id % 2) == 0) { /* we return a 500 on the first query only */ conn->submitError(frame->hd.stream_id, 500, "Server failure"); } else if (qname == DNSName("wrong-stream-id.powerdns.com.") && (id % 2) == 0) { /* we return a wrong stremad ID on the first query only */ BOOST_CHECK_EQUAL(frame->hd.stream_id, 1); conn->d_responses[frame->hd.stream_id] = expected.d_response; /* use an invalid stream ID! */ conn->d_idMapping[frame->hd.stream_id] = frame->hd.stream_id + 4; conn->submitResponse(frame->hd.stream_id, conn->d_responses.at(frame->hd.stream_id)); } else { conn->d_responses[frame->hd.stream_id] = expected.d_response; conn->submitResponse(frame->hd.stream_id, conn->d_responses.at(frame->hd.stream_id)); } conn->d_queries.erase(frame->hd.stream_id); } return 0; } static int on_data_chunk_recv_callback(nghttp2_session* session, uint8_t flags, int32_t stream_id, const uint8_t* data, size_t len, void* user_data) { DOHConnection* conn = reinterpret_cast(user_data); auto& query = conn->d_queries[stream_id]; query.insert(query.end(), data, data + len); return 0; } static int on_stream_close_callback(nghttp2_session* session, int32_t stream_id, uint32_t error_code, void* user_data) { if (error_code == 0) { return 0; } return 0; } }; static std::map> s_connectionBuffers; class MockupTLSConnection : public TLSConnection { public: MockupTLSConnection(int descriptor, bool client = false, bool needProxyProtocol = false) : d_descriptor(descriptor), d_client(client) { s_connectionBuffers[d_descriptor] = std::make_unique(needProxyProtocol); } ~MockupTLSConnection() {} IOState tryHandshake() override { auto step = getStep(); BOOST_REQUIRE_EQUAL(step.request, ExpectedStep::ExpectedRequest::handshakeClient); return step.nextState; } IOState tryWrite(const PacketBuffer& buffer, size_t& pos, size_t toWrite) override { auto& conn = s_connectionBuffers.at(d_descriptor); auto step = getStep(); BOOST_REQUIRE_EQUAL(step.request, !d_client ? ExpectedStep::ExpectedRequest::writeToClient : ExpectedStep::ExpectedRequest::writeToBackend); if (step.bytes == 0) { if (step.nextState == IOState::NeedWrite) { return step.nextState; } throw std::runtime_error("Remote host closed the connection"); } toWrite -= pos; BOOST_REQUIRE_GE(buffer.size(), pos + toWrite); if (step.bytes < toWrite) { toWrite = step.bytes; } conn->submitIncoming(buffer, pos, toWrite); pos += toWrite; return step.nextState; } IOState tryRead(PacketBuffer& buffer, size_t& pos, size_t toRead, bool allowIncomplete = false) override { auto& conn = s_connectionBuffers.at(d_descriptor); auto step = getStep(); BOOST_REQUIRE_EQUAL(step.request, !d_client ? ExpectedStep::ExpectedRequest::readFromClient : ExpectedStep::ExpectedRequest::readFromBackend); if (step.bytes == 0) { if (step.nextState == IOState::NeedRead) { return step.nextState; } throw std::runtime_error("Remote host closed the connection"); } auto& externalBuffer = conn->d_serverOutBuffer; toRead -= pos; if (step.bytes < toRead) { toRead = step.bytes; } if (allowIncomplete) { if (toRead > externalBuffer.size()) { toRead = externalBuffer.size(); } } else { BOOST_REQUIRE_GE(externalBuffer.size(), toRead); } BOOST_REQUIRE_GE(buffer.size(), toRead); std::copy(externalBuffer.begin(), externalBuffer.begin() + toRead, buffer.begin() + pos); pos += toRead; externalBuffer.erase(externalBuffer.begin(), externalBuffer.begin() + toRead); return step.nextState; } IOState tryConnect(bool fastOpen, const ComboAddress& remote) override { auto step = getStep(); BOOST_REQUIRE_EQUAL(step.request, ExpectedStep::ExpectedRequest::connectToBackend); return step.nextState; } void close() override { auto step = getStep(); BOOST_REQUIRE_EQUAL(step.request, !d_client ? ExpectedStep::ExpectedRequest::closeClient : ExpectedStep::ExpectedRequest::closeBackend); } bool hasBufferedData() const override { return false; } bool isUsable() const override { return true; } std::string getServerNameIndication() const override { return ""; } std::vector getNextProtocol() const override { return std::vector(); } LibsslTLSVersion getTLSVersion() const override { return LibsslTLSVersion::TLS13; } bool hasSessionBeenResumed() const override { return false; } std::vector> getSessions() override { return {}; } void setSession(std::unique_ptr& session) override { } /* unused in that context, don't bother */ void doHandshake() override { } void connect(bool fastOpen, const ComboAddress& remote, const struct timeval& timeout) override { } size_t read(void* buffer, size_t bufferSize, const struct timeval& readTimeout, const struct timeval& totalTimeout = {0, 0}, bool allowIncomplete = false) override { return 0; } size_t write(const void* buffer, size_t bufferSize, const struct timeval& writeTimeout) override { return 0; } private: ExpectedStep getStep() const { BOOST_REQUIRE(!s_steps.empty()); auto step = s_steps.front(); s_steps.pop_front(); if (step.cb) { step.cb(d_descriptor); } return step; } const int d_descriptor; bool d_client{false}; }; class MockupTLSCtx : public TLSCtx { public: ~MockupTLSCtx() { } std::unique_ptr getConnection(int socket, const struct timeval& timeout, time_t now) override { return std::make_unique(socket); } std::unique_ptr getClientConnection(const std::string& host, int socket, const struct timeval& timeout) override { return std::make_unique(socket, true, d_needProxyProtocol); } void rotateTicketsKey(time_t now) override { } size_t getTicketsKeysCount() override { return 0; } std::string getName() const override { return "Mockup TLS"; } bool d_needProxyProtocol{false}; }; class MockupFDMultiplexer : public FDMultiplexer { public: MockupFDMultiplexer() { } ~MockupFDMultiplexer() { } int run(struct timeval* tv, int timeout = 500) override { int ret = 0; gettimeofday(tv, nullptr); // MANDATORY /* 'ready' might be altered by a callback while we are iterating */ const auto readyFDs = ready; for (const auto fd : readyFDs) { { const auto& it = d_readCallbacks.find(fd); if (it != d_readCallbacks.end()) { it->d_callback(it->d_fd, it->d_parameter); } } { const auto& it = d_writeCallbacks.find(fd); if (it != d_writeCallbacks.end()) { it->d_callback(it->d_fd, it->d_parameter); } } } return ret; } void getAvailableFDs(std::vector& fds, int timeout) override { } void addFD(int fd, FDMultiplexer::EventKind kind) override { } void removeFD(int fd, FDMultiplexer::EventKind) override { } string getName() const override { return "mockup"; } void setReady(int fd) { ready.insert(fd); } void setNotReady(int fd) { ready.erase(fd); } private: std::set ready; }; class MockupQuerySender : public TCPQuerySender { public: bool active() const override { return true; } const ClientState* getClientState() const override { return nullptr; } void handleResponse(const struct timeval& now, TCPResponse&& response) override { if (d_customHandler) { d_customHandler(d_id, now, std::move(response)); return; } BOOST_REQUIRE_GT(response.d_buffer.size(), sizeof(dnsheader)); auto dh = reinterpret_cast(response.d_buffer.data()); uint16_t id = ntohs(dh->id); BOOST_REQUIRE_EQUAL(id, d_id); const auto& expected = s_responses.at(id); BOOST_REQUIRE_EQUAL(expected.d_response.size(), response.d_buffer.size()); for (size_t idx = 0; idx < response.d_buffer.size(); idx++) { if (expected.d_response.at(idx) != response.d_buffer.at(idx)) { cerr << "Mismatch at offset " << idx << ", expected " << std::to_string(response.d_buffer.at(idx)) << " got " << std::to_string(expected.d_response.at(idx)) << endl; BOOST_CHECK(false); } } if (expected.d_response != response.d_buffer) { BOOST_REQUIRE(false); } d_valid = true; } void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override { } void notifyIOError(IDState&& query, const struct timeval& now) override { d_error = true; } std::function d_customHandler; uint16_t d_id{0}; bool d_valid{false}; bool d_error{false}; }; static bool isIPv6Supported() { try { ComboAddress addr("[2001:db8:53::1]:53"); auto socket = std::make_unique(addr.sin4.sin_family, SOCK_STREAM, 0); socket->setNonBlocking(); int res = SConnectWithTimeout(socket->getHandle(), addr, timeval{0, 0}); if (res == 0 || res == EINPROGRESS) { return true; } return false; } catch (const std::exception& e) { return false; } } static ComboAddress getBackendAddress(const std::string& lastDigit, uint16_t port) { static const bool useV6 = isIPv6Supported(); if (useV6) { return ComboAddress("2001:db8:53::" + lastDigit, port); } return ComboAddress("192.0.2." + lastDigit, port); } static std::unique_ptr s_mplexer; struct TestFixture { TestFixture() { s_steps.clear(); s_responses.clear(); s_mplexer = std::make_unique(); } ~TestFixture() { clearH2Connections(); s_steps.clear(); s_responses.clear(); s_mplexer.reset(); } }; BOOST_FIXTURE_TEST_CASE(test_SingleQuery, TestFixture) { auto local = getBackendAddress("1", 80); ClientState localCS(local, true, false, false, "", {}); auto tlsCtx = std::make_shared(); localCS.tlsFrontend = std::make_shared(tlsCtx); struct timeval now; gettimeofday(&now, nullptr); size_t counter = 1; DNSName name("powerdns.com."); PacketBuffer query; GenericDNSPacketWriter pwQ(query, name, QType::A, QClass::IN, 0); pwQ.getHeader()->rd = 1; pwQ.getHeader()->id = htons(counter); PacketBuffer response; GenericDNSPacketWriter pwR(response, name, QType::A, QClass::IN, 0); pwR.getHeader()->qr = 1; pwR.getHeader()->rd = 1; pwR.getHeader()->ra = 1; pwR.getHeader()->id = htons(counter); pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER); pwR.xfr32BitInt(0x01020304); pwR.commit(); s_responses[counter] = {query, response}; auto backend = std::make_shared(getBackendAddress("42", 53)); backend->d_tlsCtx = tlsCtx; backend->d_tlsSubjectName = "backend.powerdns.com"; backend->d_dohPath = "/dns-query"; backend->d_addXForwardedHeaders = true; auto sender = std::make_shared(); sender->d_id = counter; InternalQuery internalQuery(std::move(query), IDState()); s_steps = { {ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done}, /* opening */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* read settings, headers and response from the server */ {ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as NOT ready anymore */ dynamic_cast(s_mplexer.get())->setNotReady(desc); }}, /* acknowledge settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { s_connectionBuffers.at(desc)->submitGoAway(); dynamic_cast(s_mplexer.get())->setReady(desc); }}, {ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits::max()}, {ExpectedStep::ExpectedRequest::closeBackend, IOState::Done}, }; auto sliced = std::shared_ptr(sender); bool result = sendH2Query(backend, s_mplexer, sliced, std::move(internalQuery), false); BOOST_CHECK_EQUAL(result, true); while (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0) { s_mplexer->run(&now); } BOOST_CHECK_EQUAL(sender->d_valid, true); } BOOST_FIXTURE_TEST_CASE(test_ConcurrentQueries, TestFixture) { auto local = getBackendAddress("1", 80); ClientState localCS(local, true, false, false, "", {}); auto tlsCtx = std::make_shared(); localCS.tlsFrontend = std::make_shared(tlsCtx); struct timeval now; gettimeofday(&now, nullptr); auto backend = std::make_shared(getBackendAddress("42", 53)); backend->d_tlsCtx = tlsCtx; backend->d_tlsSubjectName = "backend.powerdns.com"; backend->d_dohPath = "/dns-query"; backend->d_addXForwardedHeaders = true; size_t numberOfQueries = 2; std::vector, InternalQuery>> queries; for (size_t counter = 0; counter < numberOfQueries; counter++) { DNSName name("powerdns.com."); PacketBuffer query; GenericDNSPacketWriter pwQ(query, name, QType::A, QClass::IN, 0); pwQ.getHeader()->rd = 1; pwQ.getHeader()->id = htons(counter); PacketBuffer response; GenericDNSPacketWriter pwR(response, name, QType::A, QClass::IN, 0); pwR.getHeader()->qr = 1; pwR.getHeader()->rd = 1; pwR.getHeader()->ra = 1; pwR.getHeader()->id = htons(counter); pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER); pwR.xfr32BitInt(0x01020304); pwR.commit(); s_responses[counter] = {query, response}; auto sender = std::make_shared(); sender->d_id = counter; InternalQuery internalQuery(std::move(query), IDState()); queries.push_back({std::move(sender), std::move(internalQuery)}); } s_steps = { {ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done}, /* opening */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* read settings, headers and responses from the server */ {ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits::max()}, /* acknowledge settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { s_connectionBuffers.at(desc)->submitGoAway(); dynamic_cast(s_mplexer.get())->setReady(desc); }}, {ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits::max()}, {ExpectedStep::ExpectedRequest::closeBackend, IOState::Done}, }; for (auto& query : queries) { auto sliced = std::static_pointer_cast(query.first); bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false); BOOST_CHECK_EQUAL(result, true); } while (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0) { s_mplexer->run(&now); } for (auto& query : queries) { BOOST_CHECK_EQUAL(query.first->d_valid, true); } } BOOST_FIXTURE_TEST_CASE(test_ConnectionReuse, TestFixture) { auto local = getBackendAddress("1", 80); ClientState localCS(local, true, false, false, "", {}); auto tlsCtx = std::make_shared(); localCS.tlsFrontend = std::make_shared(tlsCtx); struct timeval now; gettimeofday(&now, nullptr); auto backend = std::make_shared(getBackendAddress("42", 53)); backend->d_tlsCtx = tlsCtx; backend->d_tlsSubjectName = "backend.powerdns.com"; backend->d_dohPath = "/dns-query"; backend->d_addXForwardedHeaders = true; size_t numberOfQueries = 2; std::vector, InternalQuery>> queries; for (size_t counter = 0; counter < numberOfQueries; counter++) { DNSName name("powerdns.com."); PacketBuffer query; GenericDNSPacketWriter pwQ(query, name, QType::A, QClass::IN, 0); pwQ.getHeader()->rd = 1; pwQ.getHeader()->id = htons(counter); PacketBuffer response; GenericDNSPacketWriter pwR(response, name, QType::A, QClass::IN, 0); pwR.getHeader()->qr = 1; pwR.getHeader()->rd = 1; pwR.getHeader()->ra = 1; pwR.getHeader()->id = htons(counter); pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER); pwR.xfr32BitInt(0x01020304); pwR.commit(); s_responses[counter] = {query, response}; auto sender = std::make_shared(); sender->d_id = counter; InternalQuery internalQuery(std::move(query), IDState()); queries.push_back({std::move(sender), std::move(internalQuery)}); } bool firstQueryDone = false; s_steps = { {ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done}, /* opening */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* read settings, headers and responses from the server */ {ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits::max()}, /* acknowledge settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [&firstQueryDone](int desc) { firstQueryDone = true; }}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { }}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* read settings, headers and responses from the server */ {ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits::max()}, /* later the backend sends a go away frame */ {ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { s_connectionBuffers.at(desc)->submitGoAway(); }}, {ExpectedStep::ExpectedRequest::closeBackend, IOState::Done}, }; { auto& query = queries.at(0); auto sliced = std::static_pointer_cast(query.first); bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false); BOOST_CHECK_EQUAL(result, true); while (!firstQueryDone && (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0)) { s_mplexer->run(&now); } BOOST_CHECK_EQUAL(query.first->d_valid, true); BOOST_CHECK_EQUAL(firstQueryDone, true); } { auto& query = queries.at(1); auto sliced = std::static_pointer_cast(query.first); bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false); BOOST_CHECK_EQUAL(result, true); while (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0) { s_mplexer->run(&now); } BOOST_CHECK_EQUAL(query.first->d_valid, true); } } BOOST_FIXTURE_TEST_CASE(test_InvalidDNSAnswer, TestFixture) { auto local = getBackendAddress("1", 80); ClientState localCS(local, true, false, false, "", {}); auto tlsCtx = std::make_shared(); localCS.tlsFrontend = std::make_shared(tlsCtx); struct timeval now; gettimeofday(&now, nullptr); size_t counter = 1; DNSName name("powerdns.com."); PacketBuffer query; GenericDNSPacketWriter pwQ(query, name, QType::A, QClass::IN, 0); pwQ.getHeader()->rd = 1; pwQ.getHeader()->id = htons(counter); PacketBuffer response; GenericDNSPacketWriter pwR(response, name, QType::A, QClass::IN, 0); pwR.getHeader()->qr = 1; pwR.getHeader()->rd = 1; pwR.getHeader()->ra = 1; pwR.getHeader()->id = htons(counter); pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER); pwR.xfr32BitInt(0x01020304); pwR.commit(); /* TRUNCATE the answer */ response.resize(11); s_responses[counter] = {query, response}; auto backend = std::make_shared(getBackendAddress("42", 53)); backend->d_tlsCtx = tlsCtx; backend->d_tlsSubjectName = "backend.powerdns.com"; backend->d_dohPath = "/dns-query"; backend->d_addXForwardedHeaders = true; auto sender = std::make_shared(); sender->d_id = counter; sender->d_customHandler = [](uint16_t id, const struct timeval&, TCPResponse&& resp) { BOOST_CHECK_EQUAL(resp.d_buffer.size(), 11U); /* simulate an exception, since DoH and UDP frontends will process the query right away, while TCP and DoT will first pass it back to the TCP worker thread */ throw std::runtime_error("Invalid response"); }; InternalQuery internalQuery(std::move(query), IDState()); s_steps = { {ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done}, /* opening */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* read settings, headers and response from the server */ {ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits::max()}, /* acknowledge settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* try to read, the backend says to go away */ {ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { s_connectionBuffers.at(desc)->submitGoAway(); }}, {ExpectedStep::ExpectedRequest::closeBackend, IOState::Done}, }; auto sliced = std::shared_ptr(sender); bool result = sendH2Query(backend, s_mplexer, sliced, std::move(internalQuery), false); BOOST_CHECK_EQUAL(result, true); while (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0) { s_mplexer->run(&now); } BOOST_CHECK_EQUAL(sender->d_valid, false); } BOOST_FIXTURE_TEST_CASE(test_TimeoutWhileWriting, TestFixture) { auto local = getBackendAddress("1", 80); ClientState localCS(local, true, false, false, "", {}); auto tlsCtx = std::make_shared(); localCS.tlsFrontend = std::make_shared(tlsCtx); struct timeval now; gettimeofday(&now, nullptr); auto backend = std::make_shared(getBackendAddress("42", 53)); backend->d_tlsCtx = tlsCtx; backend->d_tlsSubjectName = "backend.powerdns.com"; backend->d_dohPath = "/dns-query"; backend->d_addXForwardedHeaders = true; size_t numberOfQueries = 2; std::vector, InternalQuery>> queries; for (size_t counter = 0; counter < numberOfQueries; counter++) { DNSName name("powerdns.com."); PacketBuffer query; GenericDNSPacketWriter pwQ(query, name, QType::A, QClass::IN, 0); pwQ.getHeader()->rd = 1; pwQ.getHeader()->id = htons(counter); PacketBuffer response; GenericDNSPacketWriter pwR(response, name, QType::A, QClass::IN, 0); pwR.getHeader()->qr = 1; pwR.getHeader()->rd = 1; pwR.getHeader()->ra = 1; pwR.getHeader()->id = htons(counter); pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER); pwR.xfr32BitInt(0x01020304); pwR.commit(); s_responses[counter] = {query, response}; auto sender = std::make_shared(); sender->d_id = counter; InternalQuery internalQuery(std::move(query), IDState()); queries.push_back({std::move(sender), std::move(internalQuery)}); } bool timeout = false; s_steps = { {ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done}, /* opening */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::NeedWrite, std::numeric_limits::max(), [&timeout](int desc) { timeout = true; }}, {ExpectedStep::ExpectedRequest::closeBackend, IOState::Done}, }; for (auto& query : queries) { auto sliced = std::static_pointer_cast(query.first); bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false); BOOST_CHECK_EQUAL(result, true); } while (!timeout && (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0)) { s_mplexer->run(&now); } struct timeval later = now; later.tv_sec += backend->tcpSendTimeout + 1; auto expiredConns = handleH2Timeouts(*s_mplexer, later); BOOST_CHECK_EQUAL(expiredConns, 1U); for (auto& query : queries) { BOOST_CHECK_EQUAL(query.first->d_valid, false); BOOST_CHECK_EQUAL(query.first->d_error, true); } BOOST_CHECK_EQUAL(clearH2Connections(), 0U); } BOOST_FIXTURE_TEST_CASE(test_TimeoutWhileReading, TestFixture) { auto local = getBackendAddress("1", 80); ClientState localCS(local, true, false, false, "", {}); auto tlsCtx = std::make_shared(); localCS.tlsFrontend = std::make_shared(tlsCtx); struct timeval now; gettimeofday(&now, nullptr); auto backend = std::make_shared(getBackendAddress("42", 53)); backend->d_tlsCtx = tlsCtx; backend->d_tlsSubjectName = "backend.powerdns.com"; backend->d_dohPath = "/dns-query"; backend->d_addXForwardedHeaders = true; size_t numberOfQueries = 2; std::vector, InternalQuery>> queries; for (size_t counter = 0; counter < numberOfQueries; counter++) { DNSName name("powerdns.com."); PacketBuffer query; GenericDNSPacketWriter pwQ(query, name, QType::A, QClass::IN, 0); pwQ.getHeader()->rd = 1; pwQ.getHeader()->id = htons(counter); PacketBuffer response; GenericDNSPacketWriter pwR(response, name, QType::A, QClass::IN, 0); pwR.getHeader()->qr = 1; pwR.getHeader()->rd = 1; pwR.getHeader()->ra = 1; pwR.getHeader()->id = htons(counter); pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER); pwR.xfr32BitInt(0x01020304); pwR.commit(); s_responses[counter] = {query, response}; auto sender = std::make_shared(); sender->d_id = counter; InternalQuery internalQuery(std::move(query), IDState()); queries.push_back({std::move(sender), std::move(internalQuery)}); } bool timeout = false; s_steps = { {ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done}, /* opening */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [&timeout](int desc) { /* set the timeout flag now, since the timeout occurs while waiting for the descriptor to become readable */ timeout = true; }}, {ExpectedStep::ExpectedRequest::closeBackend, IOState::Done}, }; for (auto& query : queries) { auto sliced = std::static_pointer_cast(query.first); bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false); BOOST_CHECK_EQUAL(result, true); } while (!timeout && (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0)) { s_mplexer->run(&now); } struct timeval later = now; later.tv_sec += backend->tcpRecvTimeout + 1; auto expiredConns = handleH2Timeouts(*s_mplexer, later); BOOST_CHECK_EQUAL(expiredConns, 1U); for (auto& query : queries) { BOOST_CHECK_EQUAL(query.first->d_valid, false); BOOST_CHECK_EQUAL(query.first->d_error, true); } BOOST_CHECK_EQUAL(clearH2Connections(), 0U); } BOOST_FIXTURE_TEST_CASE(test_ShortWrite, TestFixture) { auto local = getBackendAddress("1", 80); ClientState localCS(local, true, false, false, "", {}); auto tlsCtx = std::make_shared(); localCS.tlsFrontend = std::make_shared(tlsCtx); struct timeval now; gettimeofday(&now, nullptr); auto backend = std::make_shared(getBackendAddress("42", 53)); backend->d_tlsCtx = tlsCtx; backend->d_tlsSubjectName = "backend.powerdns.com"; backend->d_dohPath = "/dns-query"; backend->d_addXForwardedHeaders = true; size_t numberOfQueries = 2; std::vector, InternalQuery>> queries; for (size_t counter = 0; counter < numberOfQueries; counter++) { DNSName name("powerdns.com."); PacketBuffer query; GenericDNSPacketWriter pwQ(query, name, QType::A, QClass::IN, 0); pwQ.getHeader()->rd = 1; pwQ.getHeader()->id = htons(counter); PacketBuffer response; GenericDNSPacketWriter pwR(response, name, QType::A, QClass::IN, 0); pwR.getHeader()->qr = 1; pwR.getHeader()->rd = 1; pwR.getHeader()->ra = 1; pwR.getHeader()->id = htons(counter); pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER); pwR.xfr32BitInt(0x01020304); pwR.commit(); s_responses[counter] = {query, response}; auto sender = std::make_shared(); sender->d_id = counter; InternalQuery internalQuery(std::move(query), IDState()); queries.push_back({std::move(sender), std::move(internalQuery)}); } bool done = false; s_steps = { {ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done}, /* opening */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::NeedWrite, 2, [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* settings (second attempt) + headers + data + headers (second query) + data */ { ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), }, /* read settings, headers and responses from the server */ {ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits::max()}, /* acknowledge settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [&done](int desc) { /* mark backend as not ready */ dynamic_cast(s_mplexer.get())->setNotReady(desc); done = true; }}, {ExpectedStep::ExpectedRequest::closeBackend, IOState::Done}, }; for (auto& query : queries) { auto sliced = std::static_pointer_cast(query.first); bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false); BOOST_CHECK_EQUAL(result, true); } while (!done && (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0)) { s_mplexer->run(&now); } for (auto& query : queries) { BOOST_CHECK_EQUAL(query.first->d_valid, true); } BOOST_CHECK_EQUAL(clearH2Connections(), 1U); } BOOST_FIXTURE_TEST_CASE(test_ShortRead, TestFixture) { auto local = getBackendAddress("1", 80); ClientState localCS(local, true, false, false, "", {}); auto tlsCtx = std::make_shared(); localCS.tlsFrontend = std::make_shared(tlsCtx); struct timeval now; gettimeofday(&now, nullptr); auto backend = std::make_shared(getBackendAddress("42", 53)); backend->d_tlsCtx = tlsCtx; backend->d_tlsSubjectName = "backend.powerdns.com"; backend->d_dohPath = "/dns-query"; backend->d_addXForwardedHeaders = true; size_t numberOfQueries = 2; std::vector, InternalQuery>> queries; for (size_t counter = 0; counter < numberOfQueries; counter++) { DNSName name("powerdns.com."); PacketBuffer query; GenericDNSPacketWriter pwQ(query, name, QType::A, QClass::IN, 0); pwQ.getHeader()->rd = 1; pwQ.getHeader()->id = htons(counter); PacketBuffer response; GenericDNSPacketWriter pwR(response, name, QType::A, QClass::IN, 0); pwR.getHeader()->qr = 1; pwR.getHeader()->rd = 1; pwR.getHeader()->ra = 1; pwR.getHeader()->id = htons(counter); pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER); pwR.xfr32BitInt(0x01020304); pwR.commit(); s_responses[counter] = {query, response}; auto sender = std::make_shared(); sender->d_id = counter; InternalQuery internalQuery(std::move(query), IDState()); queries.push_back({std::move(sender), std::move(internalQuery)}); } bool done = false; s_steps = { {ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done}, /* opening */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* read settings, headers and responses from the server */ {ExpectedStep::ExpectedRequest::readFromBackend, IOState::NeedRead, 4}, /* read settings, headers and responses (second attempt) */ {ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits::max()}, /* acknowledge settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [&done](int desc) { /* mark backend as not ready */ dynamic_cast(s_mplexer.get())->setNotReady(desc); done = true; }}, {ExpectedStep::ExpectedRequest::closeBackend, IOState::Done}, }; for (auto& query : queries) { auto sliced = std::static_pointer_cast(query.first); bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false); BOOST_CHECK_EQUAL(result, true); } while (!done && (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0)) { s_mplexer->run(&now); } for (auto& query : queries) { BOOST_CHECK_EQUAL(query.first->d_valid, true); } BOOST_CHECK_EQUAL(clearH2Connections(), 1U); } BOOST_FIXTURE_TEST_CASE(test_ConnectionClosedWhileReading, TestFixture) { auto local = getBackendAddress("1", 80); ClientState localCS(local, true, false, false, "", {}); auto tlsCtx = std::make_shared(); localCS.tlsFrontend = std::make_shared(tlsCtx); struct timeval now; gettimeofday(&now, nullptr); auto backend = std::make_shared(getBackendAddress("42", 53)); backend->d_tlsCtx = tlsCtx; backend->d_tlsSubjectName = "backend.powerdns.com"; backend->d_dohPath = "/dns-query"; backend->d_addXForwardedHeaders = true; size_t numberOfQueries = 2; std::vector, InternalQuery>> queries; for (size_t counter = 0; counter < numberOfQueries; counter++) { DNSName name("powerdns.com."); PacketBuffer query; GenericDNSPacketWriter pwQ(query, name, QType::A, QClass::IN, 0); pwQ.getHeader()->rd = 1; pwQ.getHeader()->id = htons(counter); PacketBuffer response; GenericDNSPacketWriter pwR(response, name, QType::A, QClass::IN, 0); pwR.getHeader()->qr = 1; pwR.getHeader()->rd = 1; pwR.getHeader()->ra = 1; pwR.getHeader()->id = htons(counter); pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER); pwR.xfr32BitInt(0x01020304); pwR.commit(); s_responses[counter] = {query, response}; auto sender = std::make_shared(); sender->d_id = counter; InternalQuery internalQuery(std::move(query), IDState()); queries.push_back({std::move(sender), std::move(internalQuery)}); } s_steps = { {ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done}, /* opening */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* read settings, headers and responses from the server */ {ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, 0}, {ExpectedStep::ExpectedRequest::closeBackend, IOState::Done}, }; for (auto& query : queries) { auto sliced = std::static_pointer_cast(query.first); bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false); BOOST_CHECK_EQUAL(result, true); } while (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0) { s_mplexer->run(&now); } for (auto& query : queries) { BOOST_CHECK_EQUAL(query.first->d_valid, false); BOOST_CHECK_EQUAL(query.first->d_error, true); } BOOST_CHECK_EQUAL(clearH2Connections(), 0U); } BOOST_FIXTURE_TEST_CASE(test_ConnectionClosedWhileWriting, TestFixture) { auto local = getBackendAddress("1", 80); ClientState localCS(local, true, false, false, "", {}); auto tlsCtx = std::make_shared(); localCS.tlsFrontend = std::make_shared(tlsCtx); struct timeval now; gettimeofday(&now, nullptr); auto backend = std::make_shared(getBackendAddress("42", 53)); backend->d_tlsCtx = tlsCtx; backend->d_tlsSubjectName = "backend.powerdns.com"; backend->d_dohPath = "/dns-query"; backend->d_addXForwardedHeaders = true; size_t numberOfQueries = 2; std::vector, InternalQuery>> queries; for (size_t counter = 0; counter < numberOfQueries; counter++) { DNSName name("powerdns.com."); PacketBuffer query; GenericDNSPacketWriter pwQ(query, name, QType::A, QClass::IN, 0); pwQ.getHeader()->rd = 1; pwQ.getHeader()->id = htons(counter); PacketBuffer response; GenericDNSPacketWriter pwR(response, name, QType::A, QClass::IN, 0); pwR.getHeader()->qr = 1; pwR.getHeader()->rd = 1; pwR.getHeader()->ra = 1; pwR.getHeader()->id = htons(counter); pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER); pwR.xfr32BitInt(0x01020304); pwR.commit(); s_responses[counter] = {query, response}; auto sender = std::make_shared(); sender->d_id = counter; InternalQuery internalQuery(std::move(query), IDState()); queries.push_back({std::move(sender), std::move(internalQuery)}); } bool done = false; s_steps = { {ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done}, /* opening */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* headers, connection is closed by the backend */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, 0}, {ExpectedStep::ExpectedRequest::closeBackend, IOState::Done}, {ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done}, /* opening */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* read settings, headers and response from the server */ {ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits::max()}, /* acknowledge settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [&done](int desc) { /* mark backend as not ready */ dynamic_cast(s_mplexer.get())->setNotReady(desc); done = true; }}, {ExpectedStep::ExpectedRequest::closeBackend, IOState::Done}, }; for (auto& query : queries) { auto sliced = std::static_pointer_cast(query.first); bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false); BOOST_CHECK_EQUAL(result, true); } while (!done && (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0)) { s_mplexer->run(&now); } BOOST_CHECK_EQUAL(queries.at(0).first->d_valid, false); BOOST_CHECK_EQUAL(queries.at(0).first->d_error, true); BOOST_CHECK_EQUAL(queries.at(1).first->d_valid, true); BOOST_CHECK_EQUAL(queries.at(1).first->d_error, false); BOOST_CHECK_EQUAL(clearH2Connections(), 1U); } BOOST_FIXTURE_TEST_CASE(test_GoAwayFromServer, TestFixture) { auto local = getBackendAddress("1", 80); ClientState localCS(local, true, false, false, "", {}); auto tlsCtx = std::make_shared(); localCS.tlsFrontend = std::make_shared(tlsCtx); struct timeval now; gettimeofday(&now, nullptr); auto backend = std::make_shared(getBackendAddress("42", 53)); backend->d_tlsCtx = tlsCtx; backend->d_tlsSubjectName = "backend.powerdns.com"; backend->d_dohPath = "/dns-query"; backend->d_addXForwardedHeaders = true; /* set the number of reconnection attempts to a low value to not waste time */ backend->d_retries = 1; size_t numberOfQueries = 2; std::vector, InternalQuery>> queries; for (size_t counter = 0; counter < numberOfQueries; counter++) { DNSName name("goaway.powerdns.com."); PacketBuffer query; GenericDNSPacketWriter pwQ(query, name, QType::A, QClass::IN, 0); pwQ.getHeader()->rd = 1; pwQ.getHeader()->id = htons(counter); PacketBuffer response; GenericDNSPacketWriter pwR(response, name, QType::A, QClass::IN, 0); pwR.getHeader()->qr = 1; pwR.getHeader()->rd = 1; pwR.getHeader()->ra = 1; pwR.getHeader()->id = htons(counter); pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER); pwR.xfr32BitInt(0x01020304); pwR.commit(); s_responses[counter] = {query, response}; auto sender = std::make_shared(); sender->d_id = counter; InternalQuery internalQuery(std::move(query), IDState()); queries.push_back({std::move(sender), std::move(internalQuery)}); } s_steps = { {ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done}, /* opening */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* read GO AWAY from the server (1) */ {ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits::max()}, {ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done}, /* opening */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* close the first connection. It happens now because the new connection was set up first, then that one destroyed */ {ExpectedStep::ExpectedRequest::closeBackend, IOState::Done}, /* read GO AWAY from the server (1) */ {ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits::max()}, {ExpectedStep::ExpectedRequest::closeBackend, IOState::Done}, }; for (auto& query : queries) { auto sliced = std::static_pointer_cast(query.first); bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false); BOOST_CHECK_EQUAL(result, true); } while (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0) { s_mplexer->run(&now); } for (auto& query : queries) { BOOST_CHECK_EQUAL(query.first->d_valid, false); BOOST_CHECK_EQUAL(query.first->d_error, true); } BOOST_CHECK_EQUAL(clearH2Connections(), 0U); } BOOST_FIXTURE_TEST_CASE(test_HTTP500FromServer, TestFixture) { auto local = getBackendAddress("1", 80); ClientState localCS(local, true, false, false, "", {}); auto tlsCtx = std::make_shared(); localCS.tlsFrontend = std::make_shared(tlsCtx); struct timeval now; gettimeofday(&now, nullptr); auto backend = std::make_shared(getBackendAddress("42", 53)); backend->d_tlsCtx = tlsCtx; backend->d_tlsSubjectName = "backend.powerdns.com"; backend->d_dohPath = "/dns-query"; backend->d_addXForwardedHeaders = true; size_t numberOfQueries = 2; std::vector, InternalQuery>> queries; for (size_t counter = 0; counter < numberOfQueries; counter++) { DNSName name("500.powerdns.com."); PacketBuffer query; GenericDNSPacketWriter pwQ(query, name, QType::A, QClass::IN, 0); pwQ.getHeader()->rd = 1; pwQ.getHeader()->id = htons(counter); PacketBuffer response; GenericDNSPacketWriter pwR(response, name, QType::A, QClass::IN, 0); pwR.getHeader()->qr = 1; pwR.getHeader()->rd = 1; pwR.getHeader()->ra = 1; pwR.getHeader()->id = htons(counter); pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER); pwR.xfr32BitInt(0x01020304); pwR.commit(); s_responses[counter] = {query, response}; auto sender = std::make_shared(); sender->d_id = counter; InternalQuery internalQuery(std::move(query), IDState()); queries.push_back({std::move(sender), std::move(internalQuery)}); } bool done = false; s_steps = { {ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done}, /* opening */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* read settings, headers and responses from the server */ {ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits::max()}, /* acknowledge settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [&done](int desc) { /* mark backend as not ready */ dynamic_cast(s_mplexer.get())->setNotReady(desc); done = true; }}, {ExpectedStep::ExpectedRequest::closeBackend, IOState::Done}, }; for (auto& query : queries) { auto sliced = std::static_pointer_cast(query.first); bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false); BOOST_CHECK_EQUAL(result, true); } while (!done && (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0)) { s_mplexer->run(&now); } BOOST_CHECK_EQUAL(queries.at(0).first->d_valid, false); BOOST_CHECK_EQUAL(queries.at(0).first->d_error, true); BOOST_CHECK_EQUAL(queries.at(1).first->d_valid, true); BOOST_CHECK_EQUAL(queries.at(1).first->d_error, false); BOOST_CHECK_EQUAL(clearH2Connections(), 1U); } BOOST_FIXTURE_TEST_CASE(test_WrongStreamID, TestFixture) { auto local = getBackendAddress("1", 80); ClientState localCS(local, true, false, false, "", {}); auto tlsCtx = std::make_shared(); localCS.tlsFrontend = std::make_shared(tlsCtx); struct timeval now; gettimeofday(&now, nullptr); auto backend = std::make_shared(getBackendAddress("42", 53)); backend->d_tlsCtx = tlsCtx; backend->d_tlsSubjectName = "backend.powerdns.com"; backend->d_dohPath = "/dns-query"; backend->d_addXForwardedHeaders = true; size_t numberOfQueries = 2; std::vector, InternalQuery>> queries; for (size_t counter = 0; counter < numberOfQueries; counter++) { DNSName name("wrong-stream-id.powerdns.com."); PacketBuffer query; GenericDNSPacketWriter pwQ(query, name, QType::A, QClass::IN, 0); pwQ.getHeader()->rd = 1; pwQ.getHeader()->id = htons(counter); PacketBuffer response; GenericDNSPacketWriter pwR(response, name, QType::A, QClass::IN, 0); pwR.getHeader()->qr = 1; pwR.getHeader()->rd = 1; pwR.getHeader()->ra = 1; pwR.getHeader()->id = htons(counter); pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER); pwR.xfr32BitInt(0x01020304); pwR.commit(); s_responses[counter] = {query, response}; auto sender = std::make_shared(); sender->d_id = counter; InternalQuery internalQuery(std::move(query), IDState()); queries.push_back({std::move(sender), std::move(internalQuery)}); } bool timeout = false; s_steps = { {ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done}, /* opening */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* read settings, headers and responses from the server */ {ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits::max()}, /* acknowledge settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* read ends up as a time out since nghttp2 filters the frame with the wrong stream ID */ {ExpectedStep::ExpectedRequest::readFromBackend, IOState::NeedRead, 0, [&timeout](int desc) { /* set the timeout flag now, since the timeout occurs while waiting for the descriptor to become readable */ timeout = true; }}, {ExpectedStep::ExpectedRequest::closeBackend, IOState::Done}, }; for (auto& query : queries) { auto sliced = std::static_pointer_cast(query.first); bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false); BOOST_CHECK_EQUAL(result, true); } while (!timeout && (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0)) { s_mplexer->run(&now); } struct timeval later = now; later.tv_sec += backend->tcpRecvTimeout + 1; auto expiredConns = handleH2Timeouts(*s_mplexer, later); BOOST_CHECK_EQUAL(expiredConns, 1U); BOOST_CHECK_EQUAL(queries.at(0).first->d_valid, false); BOOST_CHECK_EQUAL(queries.at(0).first->d_error, true); BOOST_CHECK_EQUAL(queries.at(1).first->d_valid, false); BOOST_CHECK_EQUAL(queries.at(1).first->d_error, true); BOOST_CHECK_EQUAL(clearH2Connections(), 0U); } BOOST_FIXTURE_TEST_CASE(test_ProxyProtocol, TestFixture) { auto local = getBackendAddress("1", 80); ClientState localCS(local, true, false, false, "", {}); auto tlsCtx = std::make_shared(); tlsCtx->d_needProxyProtocol = true; localCS.tlsFrontend = std::make_shared(tlsCtx); struct timeval now; gettimeofday(&now, nullptr); auto backend = std::make_shared(getBackendAddress("42", 53)); backend->d_tlsCtx = tlsCtx; backend->d_tlsSubjectName = "backend.powerdns.com"; backend->d_dohPath = "/dns-query"; backend->d_addXForwardedHeaders = true; backend->useProxyProtocol = true; size_t numberOfQueries = 2; std::vector, InternalQuery>> queries; for (size_t counter = 0; counter < numberOfQueries; counter++) { DNSName name("powerdns.com."); PacketBuffer query; GenericDNSPacketWriter pwQ(query, name, QType::A, QClass::IN, 0); pwQ.getHeader()->rd = 1; pwQ.getHeader()->id = htons(counter); PacketBuffer response; GenericDNSPacketWriter pwR(response, name, QType::A, QClass::IN, 0); pwR.getHeader()->qr = 1; pwR.getHeader()->rd = 1; pwR.getHeader()->ra = 1; pwR.getHeader()->id = htons(counter); pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER); pwR.xfr32BitInt(0x01020304); pwR.commit(); s_responses[counter] = {query, response}; auto sender = std::make_shared(); sender->d_id = counter; std::string payload = makeProxyHeader(counter % 2, local, local, {}); InternalQuery internalQuery(std::move(query), IDState()); internalQuery.d_proxyProtocolPayload = std::move(payload); queries.push_back({std::move(sender), std::move(internalQuery)}); } s_steps = { {ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done}, /* proxy protocol data + opening */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, {ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done}, /* proxy protocol data + opening */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* headers */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, /* data */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max(), [](int desc) { /* set the outgoing descriptor (backend connection) as ready */ dynamic_cast(s_mplexer.get())->setReady(desc); }}, /* read settings, headers and responses from the server */ {ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits::max()}, /* acknowledge settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, {ExpectedStep::ExpectedRequest::closeBackend, IOState::Done}, /* read settings, headers and responses from the server */ {ExpectedStep::ExpectedRequest::readFromBackend, IOState::Done, std::numeric_limits::max()}, /* acknowledge settings */ {ExpectedStep::ExpectedRequest::writeToBackend, IOState::Done, std::numeric_limits::max()}, {ExpectedStep::ExpectedRequest::closeBackend, IOState::Done}, }; for (auto& query : queries) { auto sliced = std::static_pointer_cast(query.first); bool result = sendH2Query(backend, s_mplexer, sliced, std::move(query.second), false); BOOST_CHECK_EQUAL(result, true); } while (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0) { s_mplexer->run(&now); } for (auto& query : queries) { BOOST_CHECK_EQUAL(query.first->d_valid, true); } BOOST_CHECK_EQUAL(clearH2Connections(), 0U); } BOOST_AUTO_TEST_SUITE_END(); #endif /* HAVE_NGHTTP2 */