]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Implement read-ahead support for incoming TLS connections
authorRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 21 Jun 2023 08:55:28 +0000 (10:55 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 7 Sep 2023 08:22:02 +0000 (10:22 +0200)
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.

pdns/dnsdist-lua.cc
pdns/dnsdist-tcp.cc
pdns/dnsdistdist/dnsdist-nghttp2-in.cc
pdns/dnsdistdist/dnsdist-nghttp2-in.hh
pdns/libssl.hh
pdns/tcpiohandler.cc

index fedd9c5d868a1130dbfdaaf7870be956a56c24a1..c09ff65aead40b6c0e999f715992dc421dc8fb4a 100644 (file)
@@ -226,6 +226,7 @@ static void parseTLSConfig(TLSConfig& config, const std::string& context, boost:
   getOptionalValue<bool>(vars, "enableRenegotiation", config.d_enableRenegotiation);
   getOptionalValue<bool>(vars, "tlsAsyncMode", config.d_asyncMode);
   getOptionalValue<bool>(vars, "ktls", config.d_ktls);
+  getOptionalValue<bool>(vars, "readAhead", config.d_readAhead);
 }
 
 #endif // defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
index 751ba986722a4075d1ebbc3aa75db7443c5ebae1..8d10e492575b0c506784e959f2843accacecd354 100644 (file)
@@ -375,7 +375,7 @@ void IncomingTCPConnectionState::terminateClientConnection()
 void IncomingTCPConnectionState::queueResponse(std::shared_ptr<IncomingTCPConnectionState>& 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 "<<state->d_queuedResponses.size());
 
   // when the response comes from a backend, there is a real possibility that we are currently
index 95b5705fd1422d3173a2c43fe8cdde53b92e9937..8b4f237c9f15f1e48e7a0aee479dcade2a43e02a 100644 (file)
@@ -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<IncomingHTTP2Connection*>(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<const std::string, static_cast<size_t>(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<PacketBuffer> getPayloadFromPath(const std::string_view& pa
   }
   sdns.reserve(payloadSize + neededPadding);
   sdns = path.substr(pos + 5);
-  for (auto &entry : sdns) {
+  for (autoentry : 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<size_t>(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();
   }
 }
index 1fa2b83a2ee5d91c200e6c9dcd8654cf4249708f..8d828dbb9ac89755d03fe7678efe4487507aaefe 100644 (file)
@@ -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,
index a8c30bf25a6c5f75432c59409622e8eb07ebf7a6..feb90cbea3bdb84b440ab099f302dea642ca579e 100644 (file)
@@ -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
index 78f23f9df414a15629f60aa83dd201d31859478f..0b960134971bffc4e8b59d21a10c61484fa7edea 100644 (file)
@@ -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()) {