std::shared_ptr<TCPQuerySender> d_sender{nullptr};
TCPQuery d_query;
PacketBuffer d_buffer;
+ size_t d_queryPos{0};
uint16_t d_responseCode{0};
bool d_finished{false};
};
void stopIO();
void handleResponse(PendingRequest&& request);
void handleResponseError(PendingRequest&& request, const struct timeval& now);
+ void handleIOError();
uint32_t getConcurrentStreamsCount() const;
size_t getUsageCount() const
std::unordered_map<int32_t, PendingRequest> d_currentStreams;
PacketBuffer d_out;
PacketBuffer d_in;
- size_t d_queryPos{0};
size_t d_outPos{0};
size_t d_inPos{0};
uint32_t d_highestStreamID{0};
class DownstreamDoHConnectionsManager
{
public:
- static std::shared_ptr<DoHConnectionToBackend> getConnectionToDownstream(std::unique_ptr<FDMultiplexer>& mplexer, std::shared_ptr<DownstreamState>& ds, const struct timeval& now);
+ static std::shared_ptr<DoHConnectionToBackend> getConnectionToDownstream(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& ds, const struct timeval& now);
static void releaseDownstreamConnection(std::shared_ptr<DoHConnectionToBackend>&& conn);
+ static bool removeDownstreamConnection(std::shared_ptr<DoHConnectionToBackend>& conn);
static void cleanupClosedConnections(struct timeval now);
static size_t clear();
{
struct timeval now;
gettimeofday(&now, nullptr);
- request.d_sender->handleResponse(now, TCPResponse(std::move(request.d_buffer), std::move(request.d_query.d_idstate), shared_from_this()));
+ try {
+ request.d_sender->handleResponse(now, TCPResponse(std::move(request.d_buffer), std::move(request.d_query.d_idstate), shared_from_this()));
+ }
+ catch (const std::exception& e) {
+ vinfolog("Got exception while handling response for cross-protocol DoH: %s", e.what());
+ }
}
void DoHConnectionToBackend::handleResponseError(PendingRequest&& request, const struct timeval& now)
{
- request.d_sender->notifyIOError(std::move(request.d_query.d_idstate), now);
+ try {
+ request.d_sender->notifyIOError(std::move(request.d_query.d_idstate), now);
+ }
+ catch (const std::exception& e) {
+ vinfolog("Got exception while handling response for cross-protocol DoH: %s", e.what());
+ }
}
-void DoHConnectionToBackend::handleTimeout(const struct timeval& now, bool write)
+void DoHConnectionToBackend::handleIOError()
{
d_connectionDied = true;
+ nghttp2_session_terminate_session(d_session.get(), NGHTTP2_PROTOCOL_ERROR);
+
+ struct timeval now;
+ gettimeofday(&now, nullptr);
for (auto& request : d_currentStreams) {
handleResponseError(std::move(request.second), now);
}
+
d_currentStreams.clear();
+ stopIO();
+}
+
+void DoHConnectionToBackend::handleTimeout(const struct timeval& now, bool write)
+{
+ handleIOError();
}
bool DoHConnectionToBackend::canBeReused() const
return true;
}
-#define MAKE_NV(NAME, VALUE, VALUELEN) \
- { \
- (uint8_t*)NAME, (uint8_t*)VALUE, sizeof(NAME) - 1, VALUELEN, \
- NGHTTP2_NV_FLAG_NONE \
- }
-
-#define MAKE_NV2(NAME, VALUE) \
- { \
- (uint8_t*)NAME, (uint8_t*)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \
- NGHTTP2_NV_FLAG_NONE \
- }
-
const std::unordered_map<std::string, std::string> DoHConnectionToBackend::s_constants = {
{"method-name", ":method"},
{"method-value", "POST"},
void DoHConnectionToBackend::queueQuery(std::shared_ptr<TCPQuerySender>& sender, TCPQuery&& query)
{
+ // cerr<<"in "<<__PRETTY_FUNCTION__<<" with query ID "<<ntohs(dh->id)<<endl;
auto payloadSize = std::to_string(query.d_buffer.size());
- d_currentQuery = std::move(query);
- d_queryPos = 0;
bool addXForwarded = d_ds->d_addXForwardedHeaders;
addStaticHeader(headers, "user-agent-name", "user-agent-value");
addDynamicHeader(headers, "content-length-name", payloadSize);
/* no need to add these headers for health-check queries */
- if (addXForwarded && d_currentQuery.d_idstate.origRemote.getPort() != 0) {
- remote = d_currentQuery.d_idstate.origRemote.toString();
- remotePort = std::to_string(d_currentQuery.d_idstate.origRemote.getPort());
+ if (addXForwarded && query.d_idstate.origRemote.getPort() != 0) {
+ remote = query.d_idstate.origRemote.toString();
+ remotePort = std::to_string(query.d_idstate.origRemote.getPort());
addDynamicHeader(headers, "x-forwarded-for-name", remote);
addDynamicHeader(headers, "x-forwarded-port-name", remotePort);
- if (d_currentQuery.d_idstate.cs != nullptr) {
- if (d_currentQuery.d_idstate.cs->isUDP()) {
+ if (query.d_idstate.cs != nullptr) {
+ if (query.d_idstate.cs->isUDP()) {
addStaticHeader(headers, "x-forwarded-proto-name", "x-forwarded-proto-value-dns-over-udp");
}
- else if (d_currentQuery.d_idstate.cs->isDoH()) {
- if (d_currentQuery.d_idstate.cs->hasTLS()) {
+ else if (query.d_idstate.cs->isDoH()) {
+ if (query.d_idstate.cs->hasTLS()) {
addStaticHeader(headers, "x-forwarded-proto-name", "x-forwarded-proto-value-dns-over-https");
}
else {
addStaticHeader(headers, "x-forwarded-proto-name", "x-forwarded-proto-value-dns-over-http");
}
}
- else if (d_currentQuery.d_idstate.cs->hasTLS()) {
+ else if (query.d_idstate.cs->hasTLS()) {
addStaticHeader(headers, "x-forwarded-proto-name", "x-forwarded-proto-value-dns-over-tls");
}
else {
}
}
+ PendingRequest pending;
+ pending.d_query = std::move(query);
+ pending.d_sender = std::move(sender);
+
+ uint32_t streamId = nghttp2_session_get_next_stream_id(d_session.get());
+ auto insertPair = d_currentStreams.insert({streamId, std::move(pending)});
+ if (!insertPair.second) {
+ /* there is a stream ID collision, something is very wrong! */
+ d_connectionDied = true;
+ nghttp2_session_terminate_session(d_session.get(), NGHTTP2_NO_ERROR);
+ throw std::runtime_error("Stream ID collision");
+ }
+
/* if data_prd is not NULL, it provides data which will be sent in subsequent DATA frames. In this case, a method that allows request message bodies (https://tools.ietf.org/html/rfc7231#section-4) must be specified with :method key (e.g. POST). This function does not take ownership of the data_prd. The function copies the members of the data_prd. If data_prd is NULL, HEADERS have END_STREAM set.
*/
nghttp2_data_provider data_provider;
+
+ /* we will not use this pointer */
data_provider.source.ptr = this;
data_provider.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 userData = reinterpret_cast<DoHConnectionToBackend*>(user_data);
+ auto conn = reinterpret_cast<DoHConnectionToBackend*>(user_data);
+ auto& request = conn->d_currentStreams.at(stream_id);
size_t toCopy = 0;
- if (userData->d_queryPos < userData->d_currentQuery.d_buffer.size()) {
- size_t remaining = userData->d_currentQuery.d_buffer.size() - userData->d_queryPos;
+ if (request.d_queryPos < request.d_query.d_buffer.size()) {
+ size_t remaining = request.d_query.d_buffer.size() - request.d_queryPos;
toCopy = length > remaining ? remaining : length;
- memcpy(buf, &userData->d_currentQuery.d_buffer.at(userData->d_queryPos), toCopy);
- userData->d_queryPos += toCopy;
+ memcpy(buf, &request.d_query.d_buffer.at(request.d_queryPos), toCopy);
+ request.d_queryPos += toCopy;
}
- if (userData->d_queryPos >= userData->d_currentQuery.d_buffer.size()) {
+ if (request.d_queryPos >= request.d_query.d_buffer.size()) {
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
}
return toCopy;
};
- auto stream_id = nghttp2_submit_request(d_session.get(), nullptr, headers.data(), headers.size(), &data_provider, this);
- if (stream_id < 0) {
+ auto newStreamId = nghttp2_submit_request(d_session.get(), nullptr, headers.data(), headers.size(), &data_provider, this);
+ if (newStreamId < 0) {
d_connectionDied = true;
- throw std::runtime_error("Error submitting HTTP request:" + std::string(nghttp2_strerror(stream_id)));
+ d_currentStreams.erase(streamId);
+ throw std::runtime_error("Error submitting HTTP request:" + std::string(nghttp2_strerror(newStreamId)));
}
- //cerr<<"stream ID is "<<stream_id<<" for a query of size "<<payloadSize<<endl;
+ // cerr<<"stream ID is "<<newStreamId<<" for a query of size "<<payloadSize<<endl;
auto rv = nghttp2_session_send(d_session.get());
if (rv != 0) {
d_connectionDied = true;
+ d_currentStreams.erase(streamId);
throw std::runtime_error("Error in nghttp2_session_send:" + std::to_string(rv));
}
- PendingRequest request;
- request.d_query = std::move(d_currentQuery);
- request.d_sender = std::move(sender);
- auto insertPair = d_currentStreams.insert({stream_id, std::move(request)});
- if (!insertPair.second) {
- /* there is a stream ID collision, something is very wrong! */
- d_connectionDied = true;
- nghttp2_session_terminate_session(d_session.get(), NGHTTP2_NO_ERROR);
- throw std::runtime_error("Stream ID collision");
- }
- d_highestStreamID = stream_id;
+ d_highestStreamID = newStreamId;
}
class DoHClientThreadData
do {
conn->d_inPos = 0;
conn->d_in.resize(conn->d_in.size() + 512);
- //cerr<<"trying to read "<<conn->d_in.size()<<endl;
+ // cerr<<"trying to read "<<conn->d_in.size()<<endl;
try {
IOState newState = conn->d_handler->tryRead(conn->d_in, conn->d_inPos, conn->d_in.size(), true);
- //cerr<<"got a "<<(int)newState<<" state and "<<conn->d_inPos<<" bytes"<<endl;
+ // cerr<<"got a "<<(int)newState<<" state and "<<conn->d_inPos<<" bytes"<<endl;
conn->d_in.resize(conn->d_inPos);
- if (newState == IOState::Done) {
+
+ if (conn->d_inPos > 0) {
+ /* we got something */
auto readlen = nghttp2_session_mem_recv(conn->d_session.get(), conn->d_in.data(), conn->d_inPos);
- //cerr<<"nghttp2_session_mem_recv returned "<<readlen<<endl;
+ // cerr<<"nghttp2_session_mem_recv returned "<<readlen<<endl;
/* as long as we don't require a pause by returning nghttp2_error.NGHTTP2_ERR_PAUSE from a CB,
all data should be consumed before returning */
if (readlen > 0 && static_cast<size_t>(readlen) < conn->d_inPos) {
- cerr << "Fatal error: " << nghttp2_strerror((int)readlen) << endl;
- return;
+ throw std::runtime_error("Fatal error while passing received data to nghttp2: " + std::string(nghttp2_strerror((int)readlen)));
}
+ // cerr<<"after read send"<<endl;
nghttp2_session_send(conn->d_session.get());
+ }
+
+ if (newState == IOState::Done) {
if (conn->getConcurrentStreamsCount() == 0) {
conn->stopIO();
ioGuard.release();
}
else {
if (newState == IOState::NeedWrite) {
+ // cerr<<"need write"<<endl;
conn->updateIO(IOState::NeedWrite, handleReadableIOCallback);
}
ioGuard.release();
}
}
catch (const std::exception& e) {
- cerr << "Exception while trying to read from HTTP backend connection: " << e.what() << endl;
+ vinfolog("Exception while trying to read from HTTP backend connection: %s", e.what());
+ conn->handleIOError();
break;
}
} while (conn->getConcurrentStreamsCount() > 0);
}
IOStateGuard ioGuard(conn->d_ioState);
- //cerr<<"trying to write "<<conn->d_out.size()-conn->d_outPos<<endl;
+ // cerr<<"in "<<__PRETTY_FUNCTION__<<" trying to write "<<conn->d_out.size()-conn->d_outPos<<endl;
try {
IOState newState = conn->d_handler->tryWrite(conn->d_out, conn->d_outPos, conn->d_out.size());
- //cerr<<"got a "<<(int)newState<<" state, "<<conn->d_out.size()-conn->d_outPos<<" bytes remaining"<<endl;
+ // cerr<<"got a "<<(int)newState<<" state, "<<conn->d_out.size()-conn->d_outPos<<" bytes remaining"<<endl;
if (newState == IOState::NeedRead) {
conn->updateIO(IOState::NeedRead, handleWritableIOCallback);
}
else if (newState == IOState::Done) {
+ // cerr<<"done, buffer size was "<<conn->d_out.size()<<", pos was "<<conn->d_outPos<<endl;
++conn->d_queries;
conn->d_out.clear();
conn->d_outPos = 0;
conn->stopIO();
- conn->updateIO(IOState::NeedRead, handleReadableIOCallback);
+ if (conn->getConcurrentStreamsCount() > 0) {
+ conn->updateIO(IOState::NeedRead, handleReadableIOCallback);
+ }
}
ioGuard.release();
}
catch (const std::exception& e) {
- cerr << "Exception while trying to write (ready) to HTTP backend connection: " << e.what() << endl;
+ vinfolog("Exception while trying to write (ready) to HTTP backend connection: %s", e.what());
+ conn->handleIOError();
}
}
void DoHConnectionToBackend::stopIO()
{
d_ioState->reset();
+
+ if (d_connectionDied) {
+ /* remove ourselves from the connection cache, this might mean that our
+ reference count drops to zero after that, so we need to be careful */
+ auto shared = std::dynamic_pointer_cast<DoHConnectionToBackend>(shared_from_this());
+ DownstreamDoHConnectionsManager::removeDownstreamConnection(shared);
+ }
}
void DoHConnectionToBackend::updateIO(IOState newState, FDMultiplexer::callbackfunc_t callback)
if (bufferWasEmpty) {
try {
+ // cerr<<"in "<<__PRETTY_FUNCTION__<<" trying to write "<<conn->d_out.size()-conn->d_outPos<<endl;
auto state = conn->d_handler->tryWrite(conn->d_out, conn->d_outPos, conn->d_out.size());
+ // cerr<<"got a "<<(int)state<<" state, "<<conn->d_out.size()-conn->d_outPos<<" bytes remaining"<<endl;
if (state == IOState::Done) {
++conn->d_queries;
conn->d_out.clear();
conn->d_outPos = 0;
- conn->addToIOState(IOState::NeedRead, handleReadableIOCallback);
+ conn->stopIO();
+ if (conn->getConcurrentStreamsCount() > 0) {
+ conn->updateIO(IOState::NeedRead, handleReadableIOCallback);
+ }
}
else {
conn->updateIO(state, handleWritableIOCallback);
}
}
catch (const std::exception& e) {
- cerr << "Exception while trying to write (send) to HTTP backend connection: " << e.what() << endl;
+ vinfolog("Exception while trying to write (send) to HTTP backend connection: %s", e.what());
+ conn->handleIOError();
}
}
int DoHConnectionToBackend::on_frame_recv_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data)
{
DoHConnectionToBackend* conn = reinterpret_cast<DoHConnectionToBackend*>(user_data);
- //cerr<<"Frame type is "<<std::to_string(frame->hd.type)<<endl;
+ // cerr<<"Frame type is "<<std::to_string(frame->hd.type)<<endl;
#if 0
switch (frame->hd.type) {
case NGHTTP2_HEADERS:
if ((frame->hd.type == NGHTTP2_HEADERS || frame->hd.type == NGHTTP2_DATA) && frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
auto stream = conn->d_currentStreams.find(frame->hd.stream_id);
if (stream != conn->d_currentStreams.end()) {
- //cerr<<"Stream "<<frame->hd.stream_id<<" is now finished"<<endl;
+ // cerr<<"Stream "<<frame->hd.stream_id<<" is now finished"<<endl;
stream->second.d_finished = true;
auto request = std::move(stream->second);
int DoHConnectionToBackend::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)
{
DoHConnectionToBackend* conn = reinterpret_cast<DoHConnectionToBackend*>(user_data);
- //cerr<<"Got data of size "<<len<<" for stream "<<stream_id<<endl;
+ // cerr<<"Got data of size "<<len<<" for stream "<<stream_id<<endl;
auto stream = conn->d_currentStreams.find(stream_id);
if (stream == conn->d_currentStreams.end()) {
vinfolog("Unable to match the stream ID %d to a known one!", stream_id);
return 0;
}
- cerr << "Stream " << stream_id << " closed with error_code=" << error_code << endl;
+ // cerr << "Stream " << stream_id << " closed with error_code=" << error_code << endl;
conn->d_connectionDied = true;
auto stream = conn->d_currentStreams.find(stream_id);
if (stream == conn->d_currentStreams.end()) {
/* we don't care, then */
- cerr << "we don't care" << endl;
return 0;
}
auto request = std::move(stream->second);
conn->d_currentStreams.erase(stream->first);
- //cerr<<"in "<<__PRETTY_FUNCTION__<<", looking for a connection to send a query of size "<<request.d_query.d_buffer.size()<<endl;
- auto downstream = DownstreamDoHConnectionsManager::getConnectionToDownstream(conn->d_mplexer, conn->d_ds, now);
- downstream->queueQuery(request.d_sender, std::move(request.d_query));
+ // cerr<<"Query has "<<request.d_query.d_downstreamFailures<<" failures, backend limit is "<<conn->d_ds->d_retries<<endl;
+ if (request.d_query.d_downstreamFailures < conn->d_ds->d_retries) {
+ // cerr<<"in "<<__PRETTY_FUNCTION__<<", looking for a connection to send a query of size "<<request.d_query.d_buffer.size()<<endl;
+ ++request.d_query.d_downstreamFailures;
+ auto downstream = DownstreamDoHConnectionsManager::getConnectionToDownstream(conn->d_mplexer, conn->d_ds, now);
+ downstream->queueQuery(request.d_sender, std::move(request.d_query));
+ }
+ else {
+ conn->handleResponseError(std::move(request), now);
+ }
//cerr<<"we now have "<<conn->getConcurrentStreamsCount()<<" concurrent connections"<<endl;
if (conn->getConcurrentStreamsCount() == 0) {
time_t DownstreamDoHConnectionsManager::s_nextCleanup{0};
uint16_t DownstreamDoHConnectionsManager::s_cleanupInterval{60};
+size_t DownstreamDoHConnectionsManager::clear()
+{
+ size_t result = 0;
+ for (const auto& backend : t_downstreamConnections) {
+ result += backend.second.size();
+ }
+ t_downstreamConnections.clear();
+ return result;
+}
+
+bool DownstreamDoHConnectionsManager::removeDownstreamConnection(std::shared_ptr<DoHConnectionToBackend>& conn)
+{
+ bool found = false;
+ auto backendIt = t_downstreamConnections.find(conn->getDS()->getID());
+ if (backendIt == t_downstreamConnections.end()) {
+ return found;
+ }
+
+ for (auto connIt = backendIt->second.begin(); connIt != backendIt->second.end(); ++connIt) {
+ if (*connIt == conn) {
+ backendIt->second.erase(connIt);
+ found = true;
+ break;
+ }
+ }
+
+ return found;
+}
+
void DownstreamDoHConnectionsManager::cleanupClosedConnections(struct timeval now)
{
struct timeval freshCutOff = now;
}
}
-std::shared_ptr<DoHConnectionToBackend> DownstreamDoHConnectionsManager::getConnectionToDownstream(std::unique_ptr<FDMultiplexer>& mplexer, std::shared_ptr<DownstreamState>& ds, const struct timeval& now)
+std::shared_ptr<DoHConnectionToBackend> DownstreamDoHConnectionsManager::getConnectionToDownstream(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& ds, const struct timeval& now)
{
std::shared_ptr<DoHConnectionToBackend> result;
struct timeval freshCutOff = now;
if (now.tv_sec > lastTimeoutScan) {
lastTimeoutScan = now.tv_sec;
- auto expiredReadConns = data.mplexer->getTimeouts(now, false);
- for (const auto& cbData : expiredReadConns) {
- if (cbData.second.type() == typeid(std::shared_ptr<DoHConnectionToBackend>)) {
- auto conn = boost::any_cast<std::shared_ptr<DoHConnectionToBackend>>(cbData.second);
- vinfolog("Timeout (read) from remote DoH backend %s", conn->getBackendName());
- conn->handleTimeout(now, false);
- }
- }
- auto expiredWriteConns = data.mplexer->getTimeouts(now, true);
- for (const auto& cbData : expiredWriteConns) {
- if (cbData.second.type() == typeid(std::shared_ptr<DoHConnectionToBackend>)) {
- auto conn = boost::any_cast<std::shared_ptr<DoHConnectionToBackend>>(cbData.second);
- vinfolog("Timeout (write) from remote DoH backend %s", conn->getBackendName());
- conn->handleTimeout(now, true);
- }
- }
+ handleH2Timeouts(*data.mplexer, now);
if (g_dohStatesDumpRequested > 0) {
/* just to keep things clean in the output, debug only */
struct timeval now;
gettimeofday(&now, nullptr);
- auto newConnection = std::make_shared<DoHConnectionToBackend>(ds, mplexer, now);
- newConnection->setHealthCheck(healthCheck);
- newConnection->queueQuery(sender, std::move(query));
+ if (healthCheck) {
+ /* always do health-checks over a new connection */
+ auto newConnection = std::make_shared<DoHConnectionToBackend>(ds, mplexer, now);
+ newConnection->setHealthCheck(healthCheck);
+ newConnection->queueQuery(sender, std::move(query));
+ }
+ else {
+ auto connection = DownstreamDoHConnectionsManager::getConnectionToDownstream(mplexer, ds, now);
+ connection->queueQuery(sender, std::move(query));
+ }
+
return true;
#else /* HAVE_NGHTTP2 */
return false;
#endif /* HAVE_NGHTTP2 */
}
+
+size_t clearH2Connections()
+{
+ size_t cleared = 0;
+#ifdef HAVE_NGHTTP2
+ cleared = DownstreamDoHConnectionsManager::clear();
+#endif /* HAVE_NGHTTP2 */
+ return cleared;
+}
+
+size_t handleH2Timeouts(FDMultiplexer& mplexer, const struct timeval& now)
+{
+ size_t got = 0;
+#ifdef HAVE_NGHTTP2
+ auto expiredReadConns = mplexer.getTimeouts(now, false);
+ for (const auto& cbData : expiredReadConns) {
+ if (cbData.second.type() == typeid(std::shared_ptr<DoHConnectionToBackend>)) {
+ auto conn = boost::any_cast<std::shared_ptr<DoHConnectionToBackend>>(cbData.second);
+ vinfolog("Timeout (read) from remote DoH backend %s", conn->getBackendName());
+ conn->handleTimeout(now, false);
+ ++got;
+ }
+ }
+
+ auto expiredWriteConns = mplexer.getTimeouts(now, true);
+ for (const auto& cbData : expiredWriteConns) {
+ if (cbData.second.type() == typeid(std::shared_ptr<DoHConnectionToBackend>)) {
+ auto conn = boost::any_cast<std::shared_ptr<DoHConnectionToBackend>>(cbData.second);
+ vinfolog("Timeout (write) from remote DoH backend %s", conn->getBackendName());
+ conn->handleTimeout(now, true);
+ ++got;
+ }
+ }
+#endif /* HAVE_NGHTTP2 */
+ return got;
+}
--- /dev/null
+/*
+ * 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"
+
+#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, const ExpectedStep& step)> fn = nullptr): cb(fn), request(r), nextState(n), bytes(b)
+ {
+ }
+
+ std::function<void(int descriptor, const ExpectedStep& step)> cb{nullptr};
+ ExpectedRequest request;
+ IOState nextState;
+ size_t bytes{0};
+};
+
+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;
+}
+
+struct DOHConnection
+{
+ DOHConnection(): d_session(std::unique_ptr<nghttp2_session, void (*)(nghttp2_session*)>(nullptr, nghttp2_session_del))
+ {
+ 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;
+ 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;
+
+ size_t submitIncoming(const PacketBuffer& data, size_t pos, size_t toWrite)
+ {
+ 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);
+ }
+
+ 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);
+ }
+
+ void submitGoAway()
+ {
+ int rv = nghttp2_submit_goaway(d_session.get(), NGHTTP2_FLAG_NONE, 0, NGHTTP2_INTERNAL_ERROR, nullptr, 0);
+ 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, 1U);
+ 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);
+ // cerr<<"in "<<__PRETTY_FUNCTION__<<endl;
+ auto& query = conn->d_queries[stream_id];
+ query.insert(query.end(), data, data + len);
+ // cerr<<"out "<<__PRETTY_FUNCTION__<<endl;
+ return 0;
+ }
+
+ static int on_stream_close_callback(nghttp2_session* session, int32_t stream_id, uint32_t error_code, void* user_data)
+ {
+ //DOHConnection* conn = reinterpret_cast<DOHConnection*>(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): d_descriptor(descriptor), d_client(client)
+ {
+ s_connectionBuffers[d_descriptor] = std::make_unique<DOHConnection>();
+ }
+
+ ~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);
+
+ // cerr<<"in server try read, adding "<<toRead<<" bytes from the buffer of size "<<externalBuffer.size()<<" at position "<<pos<<", buffer had a size of "<<buffer.size()<<endl;
+ std::copy(externalBuffer.begin(), externalBuffer.begin() + toRead, buffer.begin() + pos);
+ pos += toRead;
+ externalBuffer.erase(externalBuffer.begin(), externalBuffer.begin() + toRead);
+ // cerr<<"external buffer has "<<externalBuffer.size()<<" remaining"<<endl;
+
+ 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;
+ }
+
+ 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, step);
+ }
+
+ 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);
+ }
+
+ void rotateTicketsKey(time_t now) override
+ {
+ }
+
+ size_t getTicketsKeysCount() override
+ {
+ return 0;
+ }
+
+ std::string getName() const override
+ {
+ return "Mockup TLS";
+ }
+};
+
+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 std::unique_ptr<FDMultiplexer> s_mplexer;
+
+struct TestFixture
+{
+ TestFixture()
+ {
+ s_steps.clear();
+ s_responses.clear();
+ s_mplexer = std::unique_ptr<FDMultiplexer>(new MockupFDMultiplexer());
+ }
+ ~TestFixture()
+ {
+ clearH2Connections();
+ s_steps.clear();
+ s_responses.clear();
+ s_mplexer.reset();
+ }
+};
+
+BOOST_FIXTURE_TEST_CASE(test_SingleQuery, TestFixture)
+{
+ ComboAddress local("192.0.2.1:80");
+ ClientState localCS(local, true, false, false, "", {});
+ auto tlsCtx = std::make_shared<MockupTLSCtx>();
+ localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+ 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>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+ 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, const ExpectedStep& step) {
+ /* 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() },
+ { 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)
+{
+ ComboAddress local("192.0.2.1:80");
+ ClientState localCS(local, true, false, false, "", {});
+ auto tlsCtx = std::make_shared<MockupTLSCtx>();
+ localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+ struct timeval now;
+ gettimeofday(&now, nullptr);
+
+ auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+ backend->d_tlsCtx = tlsCtx;
+ 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, const ExpectedStep& step) {
+ /* 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, const ExpectedStep& step) {
+ /* 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 },
+ };
+
+ 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)
+{
+ ComboAddress local("192.0.2.1:80");
+ ClientState localCS(local, true, false, false, "", {});
+ auto tlsCtx = std::make_shared<MockupTLSCtx>();
+ localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+ struct timeval now;
+ gettimeofday(&now, nullptr);
+
+ auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+ backend->d_tlsCtx = tlsCtx;
+ 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, const ExpectedStep& step) {
+ /* 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() },
+ /* 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, const ExpectedStep& step) {
+ /* 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() },
+ { 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 (s_mplexer->getWatchedFDCount(false) != 0 || s_mplexer->getWatchedFDCount(true) != 0) {
+ s_mplexer->run(&now);
+ }
+
+ BOOST_CHECK_EQUAL(query.first->d_valid, 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)
+{
+ ComboAddress local("192.0.2.1:80");
+ ClientState localCS(local, true, false, false, "", {});
+ auto tlsCtx = std::make_shared<MockupTLSCtx>();
+ localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+ 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>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+ 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, const ExpectedStep& step) {
+ /* 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() },
+ { 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)
+{
+ ComboAddress local("192.0.2.1:80");
+ ClientState localCS(local, true, false, false, "", {});
+ auto tlsCtx = std::make_shared<MockupTLSCtx>();
+ localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+ struct timeval now;
+ gettimeofday(&now, nullptr);
+
+ auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+ backend->d_tlsCtx = tlsCtx;
+ 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, const ExpectedStep& step) {
+ 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)
+{
+ ComboAddress local("192.0.2.1:80");
+ ClientState localCS(local, true, false, false, "", {});
+ auto tlsCtx = std::make_shared<MockupTLSCtx>();
+ localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+ struct timeval now;
+ gettimeofday(&now, nullptr);
+
+ auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+ backend->d_tlsCtx = tlsCtx;
+ 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, const ExpectedStep& step) {
+ /* 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)
+{
+ ComboAddress local("192.0.2.1:80");
+ ClientState localCS(local, true, false, false, "", {});
+ auto tlsCtx = std::make_shared<MockupTLSCtx>();
+ localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+ struct timeval now;
+ gettimeofday(&now, nullptr);
+
+ auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+ backend->d_tlsCtx = tlsCtx;
+ 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::NeedWrite, 2, [](int desc, const ExpectedStep& step) {
+ /* 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() },
+ { 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(), 1U);
+}
+
+BOOST_FIXTURE_TEST_CASE(test_ShortRead, TestFixture)
+{
+ ComboAddress local("192.0.2.1:80");
+ ClientState localCS(local, true, false, false, "", {});
+ auto tlsCtx = std::make_shared<MockupTLSCtx>();
+ localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+ struct timeval now;
+ gettimeofday(&now, nullptr);
+
+ auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+ backend->d_tlsCtx = tlsCtx;
+ 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, const ExpectedStep& step) {
+ /* 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, const ExpectedStep& step) {
+ /* 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() },
+ { 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(), 1U);
+}
+
+BOOST_FIXTURE_TEST_CASE(test_ConnectionClosedWhileReading, TestFixture)
+{
+ ComboAddress local("192.0.2.1:80");
+ ClientState localCS(local, true, false, false, "", {});
+ auto tlsCtx = std::make_shared<MockupTLSCtx>();
+ localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+ struct timeval now;
+ gettimeofday(&now, nullptr);
+
+ auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+ backend->d_tlsCtx = tlsCtx;
+ 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, const ExpectedStep& step) {
+ /* 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, const ExpectedStep& step) {
+ /* 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)
+{
+ ComboAddress local("192.0.2.1:80");
+ ClientState localCS(local, true, false, false, "", {});
+ auto tlsCtx = std::make_shared<MockupTLSCtx>();
+ localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+ struct timeval now;
+ gettimeofday(&now, nullptr);
+
+ auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+ backend->d_tlsCtx = tlsCtx;
+ 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, 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, const ExpectedStep& step) {
+ /* 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() },
+ { 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);
+ }
+
+ 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)
+{
+ ComboAddress local("192.0.2.1:80");
+ ClientState localCS(local, true, false, false, "", {});
+ auto tlsCtx = std::make_shared<MockupTLSCtx>();
+ localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+ struct timeval now;
+ gettimeofday(&now, nullptr);
+
+ auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+ backend->d_tlsCtx = tlsCtx;
+ 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, const ExpectedStep& step) {
+ /* 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, const ExpectedStep& step) {
+ /* 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, const ExpectedStep& step) {
+ /* 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, const ExpectedStep& step) {
+ /* 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)
+{
+ ComboAddress local("192.0.2.1:80");
+ ClientState localCS(local, true, false, false, "", {});
+ auto tlsCtx = std::make_shared<MockupTLSCtx>();
+ localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+ struct timeval now;
+ gettimeofday(&now, nullptr);
+
+ auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+ backend->d_tlsCtx = tlsCtx;
+ 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)});
+ }
+
+ 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, const ExpectedStep& step) {
+ /* 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, const ExpectedStep& step) {
+ /* 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 },
+ };
+
+ 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);
+ }
+
+ 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)
+{
+ ComboAddress local("192.0.2.1:80");
+ ClientState localCS(local, true, false, false, "", {});
+ auto tlsCtx = std::make_shared<MockupTLSCtx>();
+ localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
+
+ struct timeval now;
+ gettimeofday(&now, nullptr);
+
+ auto backend = std::make_shared<DownstreamState>(ComboAddress("192.0.2.42:53"), ComboAddress("0.0.0.0:0"), 0, std::string(), 1, false);
+ backend->d_tlsCtx = tlsCtx;
+ 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, const ExpectedStep& step) {
+ /* 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, const ExpectedStep& step) {
+ /* 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, const ExpectedStep& step) {
+ /* 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_AUTO_TEST_SUITE_END();
+#endif /* HAVE_NGHTTP2 */