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();
{ "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" },
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;
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)
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";
+ }
+ }
}
}
{ "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);
}
return udpFD == -1;
}
+ bool hasTLS() const
+ {
+ return tlsFrontend != nullptr || dohFrontend != nullptr;
+ }
+
std::string getType() const
{
std::string result = udpFD != -1 ? "UDP" : "TCP";
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
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()) {
static std::atomic<uint64_t> s_users;
static int s_ticketsKeyIndex{-1};
+static int s_countersIndex{-1};
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();
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);
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;
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));
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;
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};
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>
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);
}
TLSConfig d_tlsConfig;
+ TLSErrorCounters d_tlsCounters;
ComboAddress d_addr;
std::string d_provider;