]> granicus.if.org Git - pdns/commitdiff
dnsdist: Add metrics about TLS handshake failures for DoH and DoT
authorRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 22 Oct 2019 15:16:53 +0000 (17:16 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 22 Oct 2019 15:16:53 +0000 (17:16 +0200)
12 files changed:
pdns/dnsdist-carbon.cc
pdns/dnsdist-console.cc
pdns/dnsdist-lua-inspection.cc
pdns/dnsdist-web.cc
pdns/dnsdist.hh
pdns/dnsdistdist/docs/reference/config.rst
pdns/dnsdistdist/doh.cc
pdns/dnsdistdist/libssl.cc
pdns/dnsdistdist/tcpiohandler.cc
pdns/doh.hh
pdns/libssl.hh
pdns/tcpiohandler.hh

index ad8dfb7cb14373c4541e93bbc23cb7ae23033c03..72473ca00d286dddfc8f36ca03d0d32ac090cb1d 100644 (file)
@@ -139,6 +139,23 @@ try
           str<<base<<"tlsresumptions" << ' ' << front->tlsResumptions.load() << " " << now << "\r\n";
           str<<base<<"tlsunknownticketkeys" << ' ' << front->tlsUnknownTicketKey.load() << " " << now << "\r\n";
           str<<base<<"tlsinactiveticketkeys" << ' ' << front->tlsInactiveTicketKey.load() << " " << now << "\r\n";
+          const TLSErrorCounters* errorCounters = nullptr;
+          if (front->tlsFrontend != nullptr) {
+            errorCounters = &front->tlsFrontend->d_tlsCounters;
+          }
+          else if (front->dohFrontend != nullptr) {
+            errorCounters = &front->dohFrontend->d_tlsCounters;
+          }
+          if (errorCounters != nullptr) {
+            str<<base<<"tlsdhkeytoosmall" << ' ' << errorCounters->d_dhKeyTooSmall << " " << now << "\r\n";
+            str<<base<<"tlsinappropriatefallback" << ' ' << errorCounters->d_inappropriateFallBack << " " << now << "\r\n";
+            str<<base<<"tlsnosharedcipher" << ' ' << errorCounters->d_noSharedCipher << " " << now << "\r\n";
+            str<<base<<"tlsunknownciphertype" << ' ' << errorCounters->d_unknownCipherType << " " << now << "\r\n";
+            str<<base<<"tlsunknownkeyexchangetype" << ' ' << errorCounters->d_unknownKeyExchangeType << " " << now << "\r\n";
+            str<<base<<"tlsunknownprotocol" << ' ' << errorCounters->d_unknownProtocol << " " << now << "\r\n";
+            str<<base<<"tlsunsupportedec" << ' ' << errorCounters->d_unsupportedEC << " " << now << "\r\n";
+            str<<base<<"tlsunsupportedprotocol" << ' ' << errorCounters->d_unsupportedProtocol << " " << now << "\r\n";
+          }
         }
 
         auto localPools = g_pools.getLocal();
index 83613b9de22706e20d38388a49ccec6b62d8f98c..75ef60d9d5cbda136c09eada772dc5d7aa80058c 100644 (file)
@@ -559,6 +559,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "showServers", true, "[{showUUIDs=false}]", "output all servers, optionally with their UUIDs" },
   { "showTCPStats", true, "", "show some statistics regarding TCP" },
   { "showTLSContexts", true, "", "list all the available TLS contexts" },
+  { "showTLSErrorCounters", true, "", "show metrics about TLS handshake failures" },
   { "showVersion", true, "", "show the current version" },
   { "shutdown", true, "", "shut down `dnsdist`" },
   { "SkipCacheAction", true, "", "Don’t lookup the cache for this query, don’t store the answer" },
index f1c453a5b5d0c0bedd42a3eeab8945f5aea0c83e..1c335f283cbe40410c224ebf78a87d2c2f012a60 100644 (file)
@@ -590,6 +590,37 @@ void setupLuaInspection()
       g_outputBuffer=ret.str();
     });
 
+  g_lua.writeFunction("showTLSErrorCounters", [] {
+      setLuaNoSideEffect();
+      ostringstream ret;
+      boost::format fmt("%-3d %-20.20s %-23d %-23d %-23d %-23d %-23d %-23d %-23d %-23d");
+
+      ret << (fmt % "#" % "Address" % "DH key too small" % "Inappropriate fallback" % "No shared cipher" % "Unknown cipher type" % "Unknown exchange type" % "Unknown protocol" % "Unsupported EC" % "Unsupported protocol") << endl;
+
+      size_t counter = 0;
+      for(const auto& f : g_frontends) {
+        if (!f->hasTLS()) {
+          continue;
+        }
+        const TLSErrorCounters* errorCounters = nullptr;
+        if (f->tlsFrontend != nullptr) {
+          errorCounters = &f->tlsFrontend->d_tlsCounters;
+        }
+        else if (f->dohFrontend != nullptr) {
+          errorCounters = &f->dohFrontend->d_tlsCounters;
+        }
+        if (errorCounters == nullptr) {
+          continue;
+        }
+
+        ret << (fmt % counter % f->local.toStringWithPort() % errorCounters->d_dhKeyTooSmall % errorCounters->d_inappropriateFallBack % errorCounters->d_noSharedCipher % errorCounters->d_unknownCipherType % errorCounters->d_unknownKeyExchangeType % errorCounters->d_unknownProtocol % errorCounters->d_unsupportedEC % errorCounters->d_unsupportedProtocol) << endl;
+        ++counter;
+      }
+      ret << endl;
+
+      g_outputBuffer=ret.str();
+    });
+
   g_lua.writeFunction("dumpStats", [] {
       setLuaNoSideEffect();
       vector<string> leftcolumn, rightcolumn;
index 34437ba48d53063b5ea8b53f3a2d9e5ed0478f47..b8f2deadcf9dc1476922966d0b824383c6d1d2ed 100644 (file)
@@ -563,6 +563,9 @@ static void connectionThread(int sock, ComboAddress remote)
         output << "# HELP " << frontsbase << "tlsinactiveticketkeys " << "Amount of TLS sessions resumed from an inactive key" << "\n";
         output << "# TYPE " << frontsbase << "tlsinactiveticketkeys " << "counter" << "\n";
 
+        output << "# HELP " << frontsbase << "tlshandshakefailures " << "Amount of TLS handshake failures" << "\n";
+        output << "# TYPE " << frontsbase << "tlshandshakefailures " << "counter" << "\n";
+
         std::map<std::string,uint64_t> frontendDuplicates;
         for (const auto& front : g_frontends) {
           if (front->udpFD == -1 && front->tcpFD == -1)
@@ -591,16 +594,37 @@ static void connectionThread(int sock, ComboAddress remote)
             output << frontsbase << "tcpcurrentconnections" << label << front->tcpCurrentConnections.load() << "\n";
             output << frontsbase << "tcpavgqueriesperconnection" << label << front->tcpAvgQueriesPerConnection.load() << "\n";
             output << frontsbase << "tcpavgconnectionduration" << label << front->tcpAvgConnectionDuration.load() << "\n";
-            output << frontsbase << "tlsnewsessions" << label << front->tlsNewSessions.load() << "\n";
-            output << frontsbase << "tlsresumptions" << label << front->tlsResumptions.load() << "\n";
-            output << frontsbase << "tlsunknownticketkeys" << label << front->tlsUnknownTicketKey.load() << "\n";
-            output << frontsbase << "tlsinactiveticketkeys" << label << front->tlsInactiveTicketKey.load() << "\n";
-
-            output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",tls=\"tls10\"} " << front->tls10queries.load() << "\n";
-            output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",tls=\"tls11\"} " << front->tls11queries.load() << "\n";
-            output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",tls=\"tls12\"} " << front->tls12queries.load() << "\n";
-            output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",tls=\"tls13\"} " << front->tls13queries.load() << "\n";
-            output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",tls=\"unknown\"} " << front->tlsUnknownqueries.load() << "\n";
+            if (front->hasTLS()) {
+              output << frontsbase << "tlsnewsessions" << label << front->tlsNewSessions.load() << "\n";
+              output << frontsbase << "tlsresumptions" << label << front->tlsResumptions.load() << "\n";
+              output << frontsbase << "tlsunknownticketkeys" << label << front->tlsUnknownTicketKey.load() << "\n";
+              output << frontsbase << "tlsinactiveticketkeys" << label << front->tlsInactiveTicketKey.load() << "\n";
+
+              output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",tls=\"tls10\"} " << front->tls10queries.load() << "\n";
+              output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",tls=\"tls11\"} " << front->tls11queries.load() << "\n";
+              output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",tls=\"tls12\"} " << front->tls12queries.load() << "\n";
+              output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",tls=\"tls13\"} " << front->tls13queries.load() << "\n";
+              output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",tls=\"unknown\"} " << front->tlsUnknownqueries.load() << "\n";
+
+              const TLSErrorCounters* errorCounters = nullptr;
+              if (front->tlsFrontend != nullptr) {
+                errorCounters = &front->tlsFrontend->d_tlsCounters;
+              }
+              else if (front->dohFrontend != nullptr) {
+                errorCounters = &front->dohFrontend->d_tlsCounters;
+              }
+
+              if (errorCounters != nullptr) {
+                output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",error=\"dhKeyTooSmall\"} " << errorCounters->d_dhKeyTooSmall << "\n";
+                output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",error=\"inappropriateFallBack\"} " << errorCounters->d_inappropriateFallBack << "\n";
+                output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",error=\"noSharedCipher\"} " << errorCounters->d_noSharedCipher << "\n";
+                output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",error=\"unknownCipherType\"} " << errorCounters->d_unknownCipherType << "\n";
+                output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",error=\"unknownKeyExchangeType\"} " << errorCounters->d_unknownKeyExchangeType << "\n";
+                output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",error=\"unknownProtocol\"} " << errorCounters->d_unknownProtocol << "\n";
+                output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",error=\"unsupportedEC\"} " << errorCounters->d_unsupportedEC << "\n";
+                output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",error=\"unsupportedProtocol{\"} " << errorCounters->d_unsupportedProtocol << "\n";
+              }
+            }
           }
         }
 
@@ -805,6 +829,23 @@ static void connectionThread(int sock, ComboAddress remote)
           { "tls13Queries", (double) front->tls13queries },
           { "tlsUnknownQueries", (double) front->tlsUnknownqueries },
         };
+        const TLSErrorCounters* errorCounters = nullptr;
+        if (front->tlsFrontend != nullptr) {
+          errorCounters = &front->tlsFrontend->d_tlsCounters;
+        }
+        else if (front->dohFrontend != nullptr) {
+          errorCounters = &front->dohFrontend->d_tlsCounters;
+        }
+        if (errorCounters != nullptr) {
+          frontend["tlsHandshakeFailuresDHKeyTooSmall"] = (double)errorCounters->d_dhKeyTooSmall;
+          frontend["tlsHandshakeFailuresInappropriateFallBack"] = (double)errorCounters->d_inappropriateFallBack;
+          frontend["tlsHandshakeFailuresNoSharedCipher"] = (double)errorCounters->d_noSharedCipher;
+          frontend["tlsHandshakeFailuresUnknownCipher"] = (double)errorCounters->d_unknownCipherType;
+          frontend["tlsHandshakeFailuresUnknownKeyExchangeType"] = (double)errorCounters->d_unknownKeyExchangeType;
+          frontend["tlsHandshakeFailuresUnknownProtocol"] = (double)errorCounters->d_unknownProtocol;
+          frontend["tlsHandshakeFailuresUnsupportedEC"] = (double)errorCounters->d_unsupportedEC;
+          frontend["tlsHandshakeFailuresUnsupportedProtocol"] = (double)errorCounters->d_unsupportedProtocol;
+        }
         frontends.push_back(frontend);
       }
 
index 3efda3575a2ad99f34d04f74ed978fd85f634419..b83959c98cfc5f3edd8dd430fbf249e40cc6d163 100644 (file)
@@ -718,6 +718,11 @@ struct ClientState
     return udpFD == -1;
   }
 
+  bool hasTLS() const
+  {
+    return tlsFrontend != nullptr || dohFrontend != nullptr;
+  }
+
   std::string getType() const
   {
     std::string result = udpFD != -1 ? "UDP" : "TCP";
index 91c820f42f6ddabd2528590fc5be7637dfc16e4b..813aa080962f116e7acccd423f947f09937267a4 100644 (file)
@@ -839,6 +839,12 @@ Status, Statistics and More
 
   Print the list of all availables DNS over TLS contexts.
 
+.. function:: showTLSErrorCounters()
+
+  .. versionadded:: 1.4.0
+
+  Display metrics about TLS handshake failures.
+
 .. function:: showVersion()
 
   Print the version of dnsdist
index 32063f181748d7343d57b070441c4787ce3869c4..8a95da7e1aa3df097dc5498427a0e6fe3df9461f 100644 (file)
@@ -962,6 +962,8 @@ static std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> getTLSContext(DOHFrontend& df
       SSL_CTX_set_tlsext_status_arg(ctx.get(), &ocspResponses);
     }
 
+    libssl_set_error_counters_callback(ctx, &df.d_tlsCounters);
+
     h2o_ssl_register_alpn_protocols(ctx.get(), h2o_http2_alpn_protocols);
 
     if (df.d_tlsConfig.d_ticketKeyFile.empty()) {
index 1c78e48c3591807f1f510243b8d039dd216ba65f..1cb9b3e2de4212958aa28fd1a12c2f39d0305627 100644 (file)
@@ -66,6 +66,7 @@ static void openssl_thread_cleanup()
 
 static std::atomic<uint64_t> s_users;
 static int s_ticketsKeyIndex{-1};
+static int s_countersIndex{-1};
 
 void registerOpenSSLUser()
 {
@@ -75,6 +76,12 @@ void registerOpenSSLUser()
     if (s_ticketsKeyIndex == -1) {
       throw std::runtime_error("Error getting an index for tickets key");
     }
+
+    s_countersIndex = SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
+
+    if (s_countersIndex == -1) {
+      throw std::runtime_error("Error getting an index for counters");
+    }
 #if (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER)
     SSL_load_error_strings();
     OpenSSL_add_ssl_algorithms();
@@ -147,6 +154,62 @@ int libssl_ticket_key_callback(SSL *s, OpenSSLTLSTicketKeysRing& keyring, unsign
   return 1;
 }
 
+static void libssl_info_callback(const SSL *ssl, int where, int ret)
+{
+  SSL_CTX* sslCtx = SSL_get_SSL_CTX(ssl);
+  if (sslCtx == nullptr) {
+    return;
+  }
+
+  TLSErrorCounters* counters = reinterpret_cast<TLSErrorCounters*>(SSL_CTX_get_ex_data(sslCtx, s_countersIndex));
+  if (counters == nullptr) {
+    return;
+  }
+
+  if (where & SSL_CB_ALERT) {
+    const long lastError = ERR_peek_last_error();
+    switch (ERR_GET_REASON(lastError)) {
+#ifdef SSL_R_DH_KEY_TOO_SMALL
+    case SSL_R_DH_KEY_TOO_SMALL:
+      ++counters->d_dhKeyTooSmall;
+      break;
+#endif /* SSL_R_DH_KEY_TOO_SMALL */
+    case SSL_R_NO_SHARED_CIPHER:
+      ++counters->d_noSharedCipher;
+      break;
+    case SSL_R_UNKNOWN_PROTOCOL:
+      ++counters->d_unknownProtocol;
+      break;
+    case SSL_R_UNSUPPORTED_PROTOCOL:
+#ifdef SSL_R_VERSION_TOO_LOW
+    case SSL_R_VERSION_TOO_LOW:
+#endif /* SSL_R_VERSION_TOO_LOW */
+      ++counters->d_unsupportedProtocol;
+      break;
+    case SSL_R_INAPPROPRIATE_FALLBACK:
+      ++counters->d_inappropriateFallBack;
+      break;
+    case SSL_R_UNKNOWN_CIPHER_TYPE:
+      ++counters->d_unknownCipherType;
+      break;
+    case SSL_R_UNKNOWN_KEY_EXCHANGE_TYPE:
+      ++counters->d_unknownKeyExchangeType;
+      break;
+    case SSL_R_UNSUPPORTED_ELLIPTIC_CURVE:
+      ++counters->d_unsupportedEC;
+      break;
+    default:
+      break;
+    }
+  }
+}
+
+void libssl_set_error_counters_callback(std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)>& ctx, TLSErrorCounters* counters)
+{
+  SSL_CTX_set_ex_data(ctx.get(), s_countersIndex, counters);
+  SSL_CTX_set_info_callback(ctx.get(), libssl_info_callback);
+}
+
 int libssl_ocsp_stapling_callback(SSL* ssl, const std::map<int, std::string>& ocspMap)
 {
   auto pkey = SSL_get_privatekey(ssl);
index 97931fa75c668efe61963dac2bed1f1c1324dc25..02b19454b8618d55b58fc54345a716923094fffa 100644 (file)
@@ -257,7 +257,7 @@ int OpenSSLTLSConnection::s_tlsConnIndex = -1;
 class OpenSSLTLSIOCtx: public TLSCtx
 {
 public:
-  OpenSSLTLSIOCtx(const TLSFrontend& fe): d_ticketKeys(fe.d_tlsConfig.d_numberOfTicketsKeys)
+  OpenSSLTLSIOCtx(TLSFrontend& fe): d_ticketKeys(fe.d_tlsConfig.d_numberOfTicketsKeys)
   {
     registerOpenSSLUser();
     d_ticketsKeyRotationDelay = fe.d_tlsConfig.d_ticketsKeyRotationDelay;
@@ -279,6 +279,8 @@ public:
       SSL_CTX_set_tlsext_status_arg(d_tlsCtx.get(), &d_ocspResponses);
     }
 
+    libssl_set_error_counters_callback(d_tlsCtx, &fe.d_tlsCounters);
+
     try {
       if (fe.d_tlsConfig.d_ticketKeyFile.empty()) {
         handleTicketsKeyRotation(time(nullptr));
@@ -725,7 +727,7 @@ private:
 class GnuTLSIOCtx: public TLSCtx
 {
 public:
-  GnuTLSIOCtx(const TLSFrontend& fe): d_creds(std::unique_ptr<gnutls_certificate_credentials_st, void(*)(gnutls_certificate_credentials_t)>(nullptr, gnutls_certificate_free_credentials)), d_enableTickets(fe.d_tlsConfig.d_enableTickets)
+  GnuTLSIOCtx(TLSFrontend& fe): d_creds(std::unique_ptr<gnutls_certificate_credentials_st, void(*)(gnutls_certificate_credentials_t)>(nullptr, gnutls_certificate_free_credentials)), d_enableTickets(fe.d_tlsConfig.d_enableTickets)
   {
     int rc = 0;
     d_ticketsKeyRotationDelay = fe.d_tlsConfig.d_ticketsKeyRotationDelay;
index 22d4747dcbfa2312a14200d6dfc4f68935fa8cce..a00ab85f268c8118f98ed98289b27932bce8110e 100644 (file)
@@ -48,6 +48,7 @@ struct DOHFrontend
   std::shared_ptr<DOHServerConfig> d_dsc{nullptr};
   std::vector<std::shared_ptr<DOHResponseMapEntry>> d_responsesMap;
   TLSConfig d_tlsConfig;
+  TLSErrorCounters d_tlsCounters;
   std::string d_serverTokens{"h2o/dnsdist"};
 #ifdef HAVE_DNS_OVER_HTTPS
   std::unique_ptr<OpenSSLTLSTicketKeysRing> d_ticketKeys{nullptr};
index 9276b74e6fbbb9e52872b64bf9f0d91859434c95..2a114b3f0b5810f8b720ccdac5beaf6cb5e54a09 100644 (file)
@@ -31,6 +31,19 @@ public:
   bool d_enableTickets{true};
 };
 
+struct TLSErrorCounters
+{
+  std::atomic<uint64_t> d_dhKeyTooSmall{0}; /* the other side sent a DH value that is not large enough */
+  std::atomic<uint64_t> d_inappropriateFallBack{0}; /* SCSV indicates that the client previously tried a higher version,
+                                                       something bad is happening */
+  std::atomic<uint64_t> d_noSharedCipher{0}; /* we could not agree on a cipher to use */
+  std::atomic<uint64_t> d_unknownCipherType{0}; /* unknown cipher type */
+  std::atomic<uint64_t> d_unknownKeyExchangeType{0}; /* * unknown exchange type, weird */
+  std::atomic<uint64_t> d_unknownProtocol{0}; /* unknown protocol (SSLv2 or TLS 1.4, who knows? */
+  std::atomic<uint64_t> d_unsupportedEC{0}; /* unsupported elliptic curve */
+  std::atomic<uint64_t> d_unsupportedProtocol{0}; /* we don't accept this TLS version, sorry */
+};
+
 #ifdef HAVE_LIBSSL
 #include <openssl/ssl.h>
 
@@ -95,6 +108,8 @@ int libssl_get_last_key_type(std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)>& ctx);
 bool libssl_generate_ocsp_response(const std::string& certFile, const std::string& caCert, const std::string& caKey, const std::string& outFile, int ndays, int nmin);
 #endif
 
+void libssl_set_error_counters_callback(std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)>& ctx, TLSErrorCounters* counters);
+
 LibsslTLSVersion libssl_tls_version_from_string(const std::string& str);
 const std::string& libssl_tls_version_to_string(LibsslTLSVersion version);
 bool libssl_set_min_tls_version(std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)>& ctx, LibsslTLSVersion version);
index b8efac21dc93558600c0a9ea43221dcd57f11d67..5a937368f0dd31fe0e545b8159f06c0e7da58e05 100644 (file)
@@ -164,6 +164,7 @@ public:
   }
 
   TLSConfig d_tlsConfig;
+  TLSErrorCounters d_tlsCounters;
   ComboAddress d_addr;
   std::string d_provider;