summaryrefslogtreecommitdiffstats
path: root/test-dnsdistnghttp2_cc.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 21:11:59 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 21:11:59 +0000
commit3cd01b932e1c85394272ae64fae67ebeda92fb00 (patch)
treec5a3115d710afc1879ddea5349362a2bc651733c /test-dnsdistnghttp2_cc.cc
parentInitial commit. (diff)
downloaddnsdist-3cd01b932e1c85394272ae64fae67ebeda92fb00.tar.xz
dnsdist-3cd01b932e1c85394272ae64fae67ebeda92fb00.zip
Adding upstream version 1.8.3.upstream/1.8.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'test-dnsdistnghttp2_cc.cc')
-rw-r--r--test-dnsdistnghttp2_cc.cc1986
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..41d9992
--- /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
+ {
+ }
+
+ std::vector<int> getAsyncFDs() override
+ {
+ return {};
+ }
+
+ /* 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, bool hostIsAddr, 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;
+ }
+
+ 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(InternalQueryState&& 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_config.d_tlsSubjectName = "backend.powerdns.com";
+ backend->d_config.d_dohPath = "/dns-query";
+ backend->d_config.d_addXForwardedHeaders = true;
+
+ auto sender = std::make_shared<MockupQuerySender>();
+ sender->d_id = counter;
+ InternalQuery internalQuery(std::move(query), InternalQueryState());
+
+ 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_config.d_tlsSubjectName = "backend.powerdns.com";
+ backend->d_config.d_dohPath = "/dns-query";
+ backend->d_config.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), InternalQueryState());
+ 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_config.d_tlsSubjectName = "backend.powerdns.com";
+ backend->d_config.d_dohPath = "/dns-query";
+ backend->d_config.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), InternalQueryState());
+ 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_config.d_tlsSubjectName = "backend.powerdns.com";
+ backend->d_config.d_dohPath = "/dns-query";
+ backend->d_config.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), InternalQueryState());
+
+ 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_config.d_tlsSubjectName = "backend.powerdns.com";
+ backend->d_config.d_dohPath = "/dns-query";
+ backend->d_config.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), InternalQueryState());
+ 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->d_config.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_config.d_tlsSubjectName = "backend.powerdns.com";
+ backend->d_config.d_dohPath = "/dns-query";
+ backend->d_config.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), InternalQueryState());
+ 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->d_config.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_config.d_tlsSubjectName = "backend.powerdns.com";
+ backend->d_config.d_dohPath = "/dns-query";
+ backend->d_config.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), InternalQueryState());
+ 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_config.d_tlsSubjectName = "backend.powerdns.com";
+ backend->d_config.d_dohPath = "/dns-query";
+ backend->d_config.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), InternalQueryState());
+ 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_config.d_tlsSubjectName = "backend.powerdns.com";
+ backend->d_config.d_dohPath = "/dns-query";
+ backend->d_config.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), InternalQueryState());
+ 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_config.d_tlsSubjectName = "backend.powerdns.com";
+ backend->d_config.d_dohPath = "/dns-query";
+ backend->d_config.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), InternalQueryState());
+ 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_config.d_tlsSubjectName = "backend.powerdns.com";
+ backend->d_config.d_dohPath = "/dns-query";
+ backend->d_config.d_addXForwardedHeaders = true;
+ /* set the number of reconnection attempts to a low value to not waste time */
+ backend->d_config.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), InternalQueryState());
+ 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_config.d_tlsSubjectName = "backend.powerdns.com";
+ backend->d_config.d_dohPath = "/dns-query";
+ backend->d_config.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), InternalQueryState());
+ 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_config.d_tlsSubjectName = "backend.powerdns.com";
+ backend->d_config.d_dohPath = "/dns-query";
+ backend->d_config.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), InternalQueryState());
+ 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->d_config.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_config.d_tlsSubjectName = "backend.powerdns.com";
+ backend->d_config.d_dohPath = "/dns-query";
+ backend->d_config.d_addXForwardedHeaders = true;
+ backend->d_config.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), InternalQueryState());
+ 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 */