]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Better detection of closed TLS downstream connections
authorRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 19 Oct 2021 15:15:47 +0000 (17:15 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 26 Oct 2021 15:07:19 +0000 (17:07 +0200)
12 files changed:
pdns/dnsdist-console.cc
pdns/dnsdist-lua.cc
pdns/dnsdist.hh
pdns/dnsdistdist/dnsdist-nghttp2.cc
pdns/dnsdistdist/dnsdist-nghttp2.hh
pdns/dnsdistdist/dnsdist-tcp-downstream.cc
pdns/dnsdistdist/dnsdist-tcp-downstream.hh
pdns/dnsdistdist/docs/reference/tuning.rst
pdns/dnsdistdist/test-dnsdistnghttp2_cc.cc
pdns/dnsdistdist/test-dnsdisttcp_cc.cc
pdns/tcpiohandler.cc
pdns/tcpiohandler.hh

index 5b27aec4d363af5a95f0afc031bc33786c38d542..68ea689e75134f05b0f0f1a2edd2c6cccb97723c 100644 (file)
@@ -585,6 +585,8 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "setConsoleMaximumConcurrentConnections", true, "max", "Set the maximum number of concurrent console connections" },
   { "setConsoleOutputMaxMsgSize", true, "messageSize", "set console message maximum size in bytes, default is 10 MB" },
   { "setDefaultBPFFilter", true, "filter", "When used at configuration time, the corresponding BPFFilter will be attached to every bind" },
+  { "setDoHDownstreamCleanupInterval", true, "interval", "minimum interval in seconds between two cleanups of the idle DoH downstream connections" },
+  { "setDoHDownstreamMaxIdleTime", true, "time", "Maximum time in seconds that a downstream DoH connection to a backend might stay idle" },
   { "setDynBlocksAction", true, "action", "set which action is performed when a query is blocked. Only DNSAction.Drop (the default) and DNSAction.Refused are supported" },
   { "setDynBlocksPurgeInterval", true, "sec", "set how often the expired dynamic block entries should be removed" },
   { "setDropEmptyQueries", true, "drop", "Whether to drop empty queries right away instead of sending a NOTIMP response" },
@@ -593,6 +595,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "setECSSourcePrefixV6", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv6 queries" },
   { "setKey", true, "key", "set access key to that key" },
   { "setLocal", true, "addr [, {doTCP=true, reusePort=false, tcpFastOpenQueueSize=0, interface=\"\", cpus={}}]", "reset the list of addresses we listen on to this address" },
+  { "setMaxCachedDoHConnectionsPerDownstream", true, "max", "Set the maximum number of inactive DoH connections to a backend cached by each worker DoH thread" },
   { "setMaxCachedTCPConnectionsPerDownstream", true, "max", "Set the maximum number of inactive TCP connections to a backend cached by each worker TCP thread" },
   { "setMaxTCPClientThreads", true, "n", "set the maximum of TCP client threads, handling TCP connections" },
   { "setMaxTCPConnectionDuration", true, "n", "set the maximum duration of an incoming TCP connection, in seconds. 0 means unlimited" },
@@ -624,6 +627,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "setStaleCacheEntriesTTL", true, "n", "allows using cache entries expired for at most n seconds when there is no backend available to answer for a query" },
   { "setSyslogFacility", true, "facility", "set the syslog logging facility to 'facility'. Defaults to LOG_DAEMON" },
   { "setTCPDownstreamCleanupInterval", true, "interval", "minimum interval in seconds between two cleanups of the idle TCP downstream connections" },
+  { "setTCPDownstreamMaxIdleTime", true, "time", "Maximum time in seconds that a downstream TCP connection to a backend might stay idle" },
   { "setTCPInternalPipeBufferSize", true, "size", "Set the size in bytes of the internal buffer of the pipes used internally to distribute connections to TCP (and DoT) workers threads" },
   { "setTCPRecvTimeout", true, "n", "set the read timeout on TCP connections from the client, in seconds" },
   { "setTCPSendTimeout", true, "n", "set the write timeout on TCP connections from the client, in seconds" },
index 07ee8f97c1100254f4b29528584fb307fdceff1a..f4e23d9c145e56454fcfbdbd606b65931d288ea0 100644 (file)
@@ -1297,7 +1297,11 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
   });
 
   luaCtx.writeFunction("setMaxCachedTCPConnectionsPerDownstream", [](size_t max) {
-    setMaxCachedTCPConnectionsPerDownstream(max);
+    DownstreamConnectionsManager::setMaxCachedConnectionsPerDownstream(max);
+  });
+
+  luaCtx.writeFunction("setMaxCachedDoHConnectionsPerDownstream", [](size_t max) {
+    setDoHDownstreamMaxConnectionsPerBackend(max);
   });
 
   luaCtx.writeFunction("setOutgoingDoHWorkerThreads", [](uint64_t workers) {
@@ -2103,6 +2107,21 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     DownstreamConnectionsManager::setCleanupInterval(interval);
   });
 
+  luaCtx.writeFunction("setDoHDownstreamCleanupInterval", [](uint16_t interval) {
+    setLuaSideEffect();
+    setDoHDownstreamCleanupInterval(interval);
+  });
+
+  luaCtx.writeFunction("setTCPDownstreamMaxIdeTime", [](uint16_t max) {
+    setLuaSideEffect();
+    DownstreamConnectionsManager::setMaxIdleTime(max);
+  });
+
+  luaCtx.writeFunction("setDoHDownstreamMaxIdeTime", [](uint16_t max) {
+    setLuaSideEffect();
+    setDoHDownstreamMaxIdleTime(max);
+  });
+
   luaCtx.writeFunction("setConsoleConnectionsLogging", [](bool enabled) {
     g_logConsoleConnections = enabled;
   });
index 6d54b94943f2b383bdf22d25cf57dae9a265bc0b..cc60aefdc3cc72f7bf5e4ea9a04fb689d3d9b627 100644 (file)
@@ -1033,7 +1033,6 @@ struct LocalHolders
 vector<std::function<void(void)>> setupLua(bool client, const std::string& config);
 
 void tcpAcceptorThread(ClientState* p);
-void setMaxCachedTCPConnectionsPerDownstream(size_t max);
 
 #ifdef HAVE_DNS_OVER_HTTPS
 void dohThread(ClientState* cs);
index 03abd4332a763ded6979d73ea410edea83e1f7cf..1536c09ec8cc5d3b511c779596ff40b57d7a734a 100644 (file)
@@ -66,6 +66,7 @@ public:
   void stopIO();
   bool reachedMaxConcurrentQueries() const override;
   bool reachedMaxStreamID() const override;
+  bool isIdle() const override;
 
 private:
   static ssize_t send_callback(nghttp2_session* session, const uint8_t* data, size_t length, int flags, void* user_data);
@@ -136,11 +137,17 @@ public:
     s_cleanupInterval = interval;
   }
 
+  static void setMaxIdleTime(uint16_t max)
+  {
+    s_maxIdleTime = max;
+  }
+
 private:
   static thread_local map<boost::uuids::uuid, std::deque<std::shared_ptr<DoHConnectionToBackend>>> t_downstreamConnections;
   static thread_local time_t t_nextCleanup;
   static size_t s_maxCachedConnectionsPerDownstream;
   static uint16_t s_cleanupInterval;
+  static uint16_t s_maxIdleTime;
 };
 
 uint32_t DoHConnectionToBackend::getConcurrentStreamsCount() const
@@ -217,6 +224,11 @@ bool DoHConnectionToBackend::reachedMaxConcurrentQueries() const
   return false;
 }
 
+bool DoHConnectionToBackend::isIdle() const
+{
+  return getConcurrentStreamsCount() == 0;
+}
+
 const std::unordered_map<std::string, std::string> DoHConnectionToBackend::s_constants = {
   {"method-name", ":method"},
   {"method-value", "POST"},
@@ -848,6 +860,7 @@ thread_local map<boost::uuids::uuid, std::deque<std::shared_ptr<DoHConnectionToB
 thread_local time_t DownstreamDoHConnectionsManager::t_nextCleanup{0};
 size_t DownstreamDoHConnectionsManager::s_maxCachedConnectionsPerDownstream{10};
 uint16_t DownstreamDoHConnectionsManager::s_cleanupInterval{60};
+uint16_t DownstreamDoHConnectionsManager::s_maxIdleTime{300};
 
 size_t DownstreamDoHConnectionsManager::clear()
 {
@@ -892,6 +905,8 @@ void DownstreamDoHConnectionsManager::cleanupClosedConnections(struct timeval no
 
   struct timeval freshCutOff = now;
   freshCutOff.tv_sec -= 1;
+  struct timeval idleCutOff = now;
+  idleCutOff.tv_sec -= s_maxIdleTime;
 
   for (auto dsIt = t_downstreamConnections.begin(); dsIt != t_downstreamConnections.end();) {
     for (auto connIt = dsIt->second.begin(); connIt != dsIt->second.end();) {
@@ -906,7 +921,13 @@ void DownstreamDoHConnectionsManager::cleanupClosedConnections(struct timeval no
         continue;
       }
 
-      if (isTCPSocketUsable((*connIt)->getHandle())) {
+      if ((*connIt)->isIdle() && (*connIt)->getLastDataReceivedTime() < idleCutOff) {
+        /* idle for too long */
+        connIt = dsIt->second.erase(connIt);
+        continue;
+      }
+
+      if ((*connIt)->isUsable()) {
         ++connIt;
       }
       else {
@@ -1326,3 +1347,18 @@ size_t handleH2Timeouts(FDMultiplexer& mplexer, const struct timeval& now)
 #endif /* HAVE_NGHTTP2 */
   return got;
 }
+
+void setDoHDownstreamCleanupInterval(uint16_t max)
+{
+  DownstreamDoHConnectionsManager::setCleanupInterval(max);
+}
+
+void setDoHDownstreamMaxIdleTime(uint16_t max)
+{
+  DownstreamDoHConnectionsManager::setMaxIdleTime(max);
+}
+
+void setDoHDownstreamMaxConnectionsPerBackend(size_t max)
+{
+  DownstreamDoHConnectionsManager::setMaxCachedConnectionsPerDownstream(max);
+}
index a44930f4cabce5e56e5c32e93e2a62d05c870617..7b7ed6333df0a2b8e22a03e42d9c4fd1528015cb 100644 (file)
@@ -69,3 +69,7 @@ bool setupDoHClientProtocolNegotiation(std::shared_ptr<TLSCtx>& ctx);
 bool sendH2Query(const std::shared_ptr<DownstreamState>& ds, std::unique_ptr<FDMultiplexer>& mplexer, std::shared_ptr<TCPQuerySender>& sender, InternalQuery&& query, bool healthCheck);
 size_t handleH2Timeouts(FDMultiplexer& mplexer, const struct timeval& now);
 size_t clearH2Connections();
+
+void setDoHDownstreamCleanupInterval(uint16_t max);
+void setDoHDownstreamMaxIdleTime(uint16_t max);
+void setDoHDownstreamMaxConnectionsPerBackend(size_t max);
index 3d48e702b2272cb0e21bca786509f0a5b2393578..505415e7e6f2192c84a4193f6a46c36ff3697d55 100644 (file)
@@ -447,7 +447,6 @@ void TCPConnectionToBackend::handleIOCallback(int fd, FDMultiplexer::funcparam_t
 
 void TCPConnectionToBackend::queueQuery(std::shared_ptr<TCPQuerySender>& sender, TCPQuery&& query)
 {
-  cerr<<"in "<<__PRETTY_FUNCTION__<<" for a query with a buffer of size "<<query.d_buffer.size()<<" and a proxy protocol payload size of "<<query.d_proxyProtocolPayload.size()<<" which has been added: "<<query.d_proxyProtocolPayloadAdded<<endl;
   if (!d_ioState) {
     d_ioState = make_unique<IOStateHandler>(*d_mplexer, d_handler->getDescriptor());
   }
@@ -803,7 +802,7 @@ std::shared_ptr<TCPConnectionToBackend> DownstreamConnectionsManager::getConnect
           return entry;
         }
 
-        if (isTCPSocketUsable(entry->getHandle())) {
+        if (entry->isUsable()) {
           ++ds->tcpReusedConnections;
           return entry;
         }
@@ -835,6 +834,8 @@ void DownstreamConnectionsManager::cleanupClosedTCPConnections(struct timeval no
 
   struct timeval freshCutOff = now;
   freshCutOff.tv_sec -= 1;
+  struct timeval idleCutOff = now;
+  idleCutOff.tv_sec -= s_maxIdleTime;
 
   for (auto dsIt = t_downstreamConnections.begin(); dsIt != t_downstreamConnections.end(); ) {
     for (auto connIt = dsIt->second.begin(); connIt != dsIt->second.end(); ) {
@@ -849,7 +850,13 @@ void DownstreamConnectionsManager::cleanupClosedTCPConnections(struct timeval no
         continue;
       }
 
-      if (isTCPSocketUsable((*connIt)->getHandle())) {
+      if ((*connIt)->isIdle() && (*connIt)->getLastDataReceivedTime() < idleCutOff) {
+        /* idle for too long */
+        connIt = dsIt->second.erase(connIt);
+        continue;
+      }
+
+      if ((*connIt)->isUsable()) {
         ++connIt;
       }
       else {
@@ -878,12 +885,8 @@ size_t DownstreamConnectionsManager::clear()
   return count;
 }
 
-void setMaxCachedTCPConnectionsPerDownstream(size_t max)
-{
-  DownstreamConnectionsManager::setMaxCachedConnectionsPerDownstream(max);
-}
-
 thread_local map<boost::uuids::uuid, std::deque<std::shared_ptr<TCPConnectionToBackend>>> DownstreamConnectionsManager::t_downstreamConnections;
 thread_local time_t DownstreamConnectionsManager::t_nextCleanup{0};
 size_t DownstreamConnectionsManager::s_maxCachedConnectionsPerDownstream{10};
 uint16_t DownstreamConnectionsManager::s_cleanupInterval{60};
+uint16_t DownstreamConnectionsManager::s_maxIdleTime{300};
index 0ea546b3dfd7e7a72adf00bf8e7967034e6b72e2..ac9d7e29cf41f2975329ab8734d93e99c6c56f7e 100644 (file)
@@ -26,6 +26,16 @@ public:
     return d_handler->getDescriptor();
   }
 
+  /* whether the underlying socket has been closed under our feet, basically */
+  bool isUsable() const
+  {
+    if (!d_handler) {
+      return false;
+    }
+
+    return d_handler->isUsable();
+  }
+
   const std::shared_ptr<DownstreamState>& getDS() const
   {
     return d_ds;
@@ -104,6 +114,7 @@ public:
 
   virtual bool reachedMaxStreamID() const = 0;
   virtual bool reachedMaxConcurrentQueries() const = 0;
+  virtual bool isIdle() const = 0;
   virtual void release()
   {
   }
@@ -214,7 +225,7 @@ public:
 
   virtual ~TCPConnectionToBackend();
 
-  bool isIdle() const
+  bool isIdle() const override
   {
     return d_state == State::idle && d_pendingQueries.size() == 0 && d_pendingResponses.size() == 0;
   }
@@ -303,9 +314,15 @@ public:
     s_cleanupInterval = interval;
   }
 
+  static void setMaxIdleTime(uint16_t max)
+  {
+    s_maxIdleTime = max;
+  }
+
 private:
   static thread_local map<boost::uuids::uuid, std::deque<std::shared_ptr<TCPConnectionToBackend>>> t_downstreamConnections;
   static thread_local time_t t_nextCleanup;
   static size_t s_maxCachedConnectionsPerDownstream;
   static uint16_t s_cleanupInterval;
+  static uint16_t s_maxIdleTime;
 };
index 86f42707fa6b58d5f34e6286f4caa55f676bbb68..5eeb400d4bff197e23b76b57a0629ef149894362 100644 (file)
@@ -1,6 +1,29 @@
 Tuning related functions
 ========================
 
+.. function:: setDoHDownstreamCleanupInterval(interval)
+
+  .. versionadded:: 1.7.0
+
+  Set how often, in seconds, the outgoing DoH connections to backends of a given worker thread are scanned to expunge the ones that are no longer usable. The default is 60 so once per minute and per worker thread.
+  :param int interval: The interval in seconds.
+
+.. function:: setDoHDownstreamMaxIdleTime(max)
+
+  .. versionadded:: 1.7.0
+
+  Set how long, in seconds, an outgoing DoH connection to a backend might stay idle before being closed. The default is 300 so 5 minutes.
+
+  :param int max: The maximum time in seconds.
+
+.. function:: setMaxCachedDoHConnectionsPerDownstream(max)
+
+  .. versionadded:: 1.7.0
+
+  Set the maximum number of inactive DoH connections to a backend cached by each DoH worker thread. These connections can be reused when a new query comes in, instead of having to establish a new connection. dnsdist regularly checks whether the other end has closed any cached connection, closing them in that case.
+
+  :param int max: The maximum number of inactive connections to keep. Default is 10, so 10 connections per backend and per DoH worker thread.
+
 .. function:: setMaxCachedTCPConnectionsPerDownstream(max)
 
   .. versionadded:: 1.6.0
@@ -84,6 +107,23 @@ Tuning related functions
 
   :param int num:
 
+.. function:: setTCPDownstreamCleanupInterval(interval)
+
+  .. versionadded:: 1.6.0
+
+  Set how often, in seconds, the outgoing TCP connections to backends of a given worker thread are scanned to expunge the ones that are no longer usable. The default is 60 so once per minute and per worker thread.
+
+  :param int interval: The interval in seconds.
+
+.. function:: setDoHDownstreamMaxIdleTime(max)
+
+  .. versionadded:: 1.7.0
+
+  Set how long, in seconds, an outgoing DoH connection to a backend might stay idle before being closed. The default is 300 so 5 minutes.
+
+  :param int max: The maximum time in seconds.
+
+
 .. function:: setTCPInternalPipeBufferSize(size)
 
   .. versionadded:: 1.6.0
index 47ab8caa1c5caf90ed431192bdc8cef9c3691f3a..7a75c023785378ec6f0df0b08af452a4c46d92f7 100644 (file)
@@ -410,6 +410,11 @@ public:
     return false;
   }
 
+  bool isUsable() const override
+  {
+    return true;
+  }
+
   std::string getServerNameIndication() const override
   {
     return "";
index 2a52e73ecdb1218a72b3da989463ac07fb9d8607..47560be1ac229a3ca4495de1cc7e4ce317d44cfa 100644 (file)
@@ -222,6 +222,11 @@ public:
     return false;
   }
 
+  bool isUsable() const override
+  {
+    return true;
+  }
+
   std::string getServerNameIndication() const override
   {
     return "";
index 05d7dbeac6e93c7ead6c210185ce9c55bb666fd0..4338f315c871ee1c6d757cad93df861cd7b747fc 100644 (file)
@@ -388,6 +388,28 @@ public:
     return false;
   }
 
+  bool isUsable() const override
+  {
+    if (!d_conn) {
+      return false;
+    }
+
+    char buf;
+    int res = SSL_peek(d_conn.get(), &buf, sizeof(buf));
+    if (res > 0) {
+      return true;
+    }
+    try {
+      convertIORequestToIOState(res);
+      return true;
+    }
+    catch (...) {
+      return false;
+    }
+
+    return false;
+  }
+
   void close() override
   {
     if (d_conn) {
@@ -1314,6 +1336,16 @@ public:
     return false;
   }
 
+  bool isUsable() const override
+  {
+    if (!d_conn) {
+      return false;
+    }
+
+    /* as far as I can tell we can't peek so we cannot do better */
+    return isTCPSocketUsable(d_socket);
+  }
+
   std::string getServerNameIndication() const override
   {
     if (d_conn) {
index ce53f4a65c4dbb5fd1c70463528b6f8893c692e1..7afbef1ffec8b512a6641528b4b3fd243a53b2b0 100644 (file)
@@ -38,6 +38,7 @@ public:
   virtual bool hasSessionBeenResumed() const = 0;
   virtual std::vector<std::unique_ptr<TLSSession>> getSessions() = 0;
   virtual void setSession(std::unique_ptr<TLSSession>& session) = 0;
+  virtual bool isUsable() const = 0;
   virtual void close() = 0;
 
   void setUnknownTicketKey()
@@ -530,6 +531,14 @@ public:
     return d_conn->getSessions();
   }
 
+  bool isUsable() const
+  {
+    if (!d_conn) {
+      return isTCPSocketUsable(d_socket);
+    }
+    return d_conn->isUsable();
+  }
+
 private:
   std::unique_ptr<TLSConnection> d_conn{nullptr};
   ComboAddress d_remote;