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