From: Remi Gacogne Date: Wed, 21 Jun 2023 08:55:28 +0000 (+0200) Subject: dnsdist: Implement read-ahead support for incoming TLS connections X-Git-Tag: rec-5.0.0-alpha1~19^2~23 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=e80c3b09f318380b6536cfdd83c07b1c8a84edd7;p=thirdparty%2Fpdns.git dnsdist: Implement read-ahead support for incoming TLS connections Read-ahead instructs OpenSSL to read more than the number of bytes we requested from the incoming connection, if possible, and to buffer it. This provides a huge performance boost by reducing the number of syscalls because in most cases the data is already available on the socket to be read even if we cannot know that yet without reading the data length. There are two drawbacks: - we can keep reading on a connection in a loop as long as there is data available, which should be prevented by our number of concurrent requests limit ; - we need to always try to read all the data available before asking the kernel to wake us up when the socket is readable, because the data buffered by OpenSSL is obviously not visible to the kernel so we could wait forever. --- diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index fedd9c5d86..c09ff65aea 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -226,6 +226,7 @@ static void parseTLSConfig(TLSConfig& config, const std::string& context, boost: getOptionalValue(vars, "enableRenegotiation", config.d_enableRenegotiation); getOptionalValue(vars, "tlsAsyncMode", config.d_asyncMode); getOptionalValue(vars, "ktls", config.d_ktls); + getOptionalValue(vars, "readAhead", config.d_readAhead); } #endif // defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS) diff --git a/pdns/dnsdist-tcp.cc b/pdns/dnsdist-tcp.cc index 751ba98672..8d10e49257 100644 --- a/pdns/dnsdist-tcp.cc +++ b/pdns/dnsdist-tcp.cc @@ -375,7 +375,7 @@ void IncomingTCPConnectionState::terminateClientConnection() void IncomingTCPConnectionState::queueResponse(std::shared_ptr& state, const struct timeval& now, TCPResponse&& response) { // queue response - state->d_queuedResponses.push_back(std::move(response)); + state->d_queuedResponses.emplace_back(std::move(response)); DEBUGLOG("queueing response, state is "<<(int)state->d_state<<", queue size is now "<d_queuedResponses.size()); // when the response comes from a backend, there is a real possibility that we are currently diff --git a/pdns/dnsdistdist/dnsdist-nghttp2-in.cc b/pdns/dnsdistdist/dnsdist-nghttp2-in.cc index 95b5705fd1..8b4f237c9f 100644 --- a/pdns/dnsdistdist/dnsdist-nghttp2-in.cc +++ b/pdns/dnsdistdist/dnsdist-nghttp2-in.cc @@ -274,6 +274,7 @@ void IncomingHTTP2Connection::handleConnectionReady() if (ret != 0) { throw std::runtime_error("Fatal error: " + std::string(nghttp2_strerror(ret))); } + d_needFlush = true; ret = nghttp2_session_send(d_session.get()); if (ret != 0) { throw std::runtime_error("Fatal error: " + std::string(nghttp2_strerror(ret))); @@ -334,7 +335,7 @@ void IncomingHTTP2Connection::handleIO() } } - if (d_state == State::waitingForQuery) { + if (d_state == State::waitingForQuery || d_state == State::idle) { readHTTPData(); } @@ -358,11 +359,11 @@ void IncomingHTTP2Connection::handleIO() ssize_t IncomingHTTP2Connection::send_callback(nghttp2_session* session, const uint8_t* data, size_t length, int flags, void* user_data) { IncomingHTTP2Connection* conn = reinterpret_cast(user_data); - bool bufferWasEmpty = conn->d_out.empty(); conn->d_out.insert(conn->d_out.end(), data, data + length); - if (bufferWasEmpty) { + if (conn->d_connectionDied || conn->d_needFlush) { try { + conn->d_needFlush = false; auto state = conn->d_handler.tryWrite(conn->d_out, conn->d_outPos, conn->d_out.size()); if (state == IOState::Done) { conn->d_out.clear(); @@ -379,7 +380,7 @@ ssize_t IncomingHTTP2Connection::send_callback(nghttp2_session* session, const u } } catch (const std::exception& e) { - vinfolog("Exception while trying to write (send) to incoming HTTP connection: %s", e.what()); + vinfolog("Exception while trying to write (send) to incoming HTTP connection to %s: %s", conn->d_ci.remote.toStringWithPort(), e.what()); conn->handleIOError(); } } @@ -412,8 +413,7 @@ static const std::array(NGHTTP2Headers::H "dns-over-tcp", "dns-over-tls", "dns-over-http", - "dns-over-https" -}; + "dns-over-https"}; static const std::string s_authorityHeaderName(":authority"); static const std::string s_pathHeaderName(":path"); @@ -500,6 +500,7 @@ bool IncomingHTTP2Connection::sendResponse(IncomingHTTP2Connection::StreamID str if (obj.d_queryPos >= obj.d_buffer.size()) { *data_flags |= NGHTTP2_DATA_FLAG_EOF; obj.d_buffer.clear(); + connection->d_needFlush = true; } return toCopy; }; @@ -691,7 +692,7 @@ static std::optional getPayloadFromPath(const std::string_view& pa } sdns.reserve(payloadSize + neededPadding); sdns = path.substr(pos + 5); - for (auto &entry : sdns) { + for (auto& entry : sdns) { switch (entry) { case '-': entry = '+'; @@ -1079,12 +1080,15 @@ int IncomingHTTP2Connection::on_error_callback(nghttp2_session* session, int lib void IncomingHTTP2Connection::readHTTPData() { + IOState newState; IOStateGuard ioGuard(d_ioState); do { size_t got = 0; - d_in.resize(d_in.size() + 512); + if (d_in.size() < 128) { + d_in.resize(std::max(static_cast(128U), d_in.capacity())); + } try { - IOState newState = d_handler.tryRead(d_in, got, d_in.size(), true); + newState = d_handler.tryRead(d_in, got, d_in.size(), true); d_in.resize(got); if (got > 0) { @@ -1100,6 +1104,9 @@ void IncomingHTTP2Connection::readHTTPData() } if (newState == IOState::Done) { + if (nghttp2_session_want_read(d_session.get())) { + continue; + } if (isIdle()) { watchForRemoteHostClosingConnection(); ioGuard.release(); @@ -1115,11 +1122,11 @@ void IncomingHTTP2Connection::readHTTPData() } } catch (const std::exception& e) { - vinfolog("Exception while trying to read from HTTP backend connection: %s", e.what()); + vinfolog("Exception while trying to read from HTTP client connection to %s: %s", d_ci.remote.toStringWithPort(), e.what()); handleIOError(); break; } - } while (getConcurrentStreamsCount() > 0); + } while (newState == IOState::Done || !isIdle()); } void IncomingHTTP2Connection::handleReadableIOCallback(int fd, FDMultiplexer::funcparam_t& param) @@ -1151,7 +1158,7 @@ void IncomingHTTP2Connection::handleWritableIOCallback(int fd, FDMultiplexer::fu ioGuard.release(); } catch (const std::exception& e) { - vinfolog("Exception while trying to write (ready) to HTTP backend connection: %s", e.what()); + vinfolog("Exception while trying to write (ready) to HTTP client connection to %s: %s", conn->d_ci.remote.toStringWithPort(), e.what()); conn->handleIOError(); } } diff --git a/pdns/dnsdistdist/dnsdist-nghttp2-in.hh b/pdns/dnsdistdist/dnsdist-nghttp2-in.hh index 1fa2b83a2e..8d828dbb9a 100644 --- a/pdns/dnsdistdist/dnsdist-nghttp2-in.hh +++ b/pdns/dnsdistdist/dnsdist-nghttp2-in.hh @@ -101,12 +101,14 @@ private: PacketBuffer d_in; size_t d_outPos{0}; bool d_connectionDied{false}; + bool d_needFlush{false}; }; class NGHTTP2Headers { public: - enum class HeaderConstantIndexes { + enum class HeaderConstantIndexes + { OK_200_VALUE = 0, METHOD_NAME, METHOD_VALUE, diff --git a/pdns/libssl.hh b/pdns/libssl.hh index a8c30bf25a..feb90cbea3 100644 --- a/pdns/libssl.hh +++ b/pdns/libssl.hh @@ -53,6 +53,8 @@ public: bool d_asyncMode{false}; /* enable kTLS mode, if supported */ bool d_ktls{false}; + /* set read ahead mode, if supported */ + bool d_readAhead{false}; }; struct TLSErrorCounters diff --git a/pdns/tcpiohandler.cc b/pdns/tcpiohandler.cc index 78f23f9df4..0b96013497 100644 --- a/pdns/tcpiohandler.cc +++ b/pdns/tcpiohandler.cc @@ -629,6 +629,10 @@ public: } #endif /* DISABLE_OCSP_STAPLING */ + if (fe.d_tlsConfig.d_readAhead) { + SSL_CTX_set_read_ahead(d_feContext->d_tlsCtx.get(), 1); + } + libssl_set_error_counters_callback(d_feContext->d_tlsCtx, &fe.d_tlsCounters); if (!fe.d_tlsConfig.d_keyLogFile.empty()) {