Add code counts and error details to debug QUIC TLS handshake failures.

NOT generated via copybara

Protected by FLAGS_quic_reloadable_flag_quic_add_ssl_error_stack_to_error_detail.

PiperOrigin-RevId: 673853213
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h
index 329cb7a..d526bac 100755
--- a/quiche/common/quiche_feature_flags_list.h
+++ b/quiche/common/quiche_feature_flags_list.h
@@ -10,6 +10,7 @@
 
 QUICHE_FLAG(bool, quiche_reloadable_flag_enable_h3_origin_frame, false, true, "If true, enables support for parsing HTTP/3 ORIGIN frames.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_act_upon_invalid_header, true, true, "If true, reject or send error response code upon receiving invalid request or response headers.")
+QUICHE_FLAG(bool, quiche_reloadable_flag_quic_add_ssl_error_stack_to_error_detail, false, false, "If true, when TlsHandshaker::AdvanceHandshake closes connection due to BoringSSL error, it will include a formatted BoringSSL error stack in the error details.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_add_stream_info_to_idle_close_detail, false, true, "If true, include stream information in idle timeout connection close detail.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_allow_client_enabled_bbr_v2, true, true, "If true, allow client to enable BBRv2 on server via connection option 'B2ON'.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_allow_host_in_request2, false, false, "If true, requests with a host header will be allowed.")
diff --git a/quiche/quic/core/crypto/crypto_utils.cc b/quiche/quic/core/crypto/crypto_utils.cc
index ad224c2..2cbdb83 100644
--- a/quiche/quic/core/crypto/crypto_utils.cc
+++ b/quiche/quic/core/crypto/crypto_utils.cc
@@ -5,6 +5,7 @@
 #include "quiche/quic/core/crypto/crypto_utils.h"
 
 #include <algorithm>
+#include <cstddef>
 #include <memory>
 #include <optional>
 #include <string>
@@ -15,6 +16,7 @@
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "openssl/bytestring.h"
+#include "openssl/err.h"
 #include "openssl/hkdf.h"
 #include "openssl/mem.h"
 #include "openssl/sha.h"
@@ -805,4 +807,29 @@
   return payload;
 }
 
+std::string CryptoUtils::GetSSLErrorStack() {
+  std::string result;
+  const char* file;
+  const char* data;
+  int line;
+  int flags;
+  int packed_error = ERR_get_error_line_data(&file, &line, &data, &flags);
+  if (packed_error != 0) {
+    char buffer[ERR_ERROR_STRING_BUF_LEN];
+    while (packed_error != 0) {
+      ERR_error_string_n(packed_error, buffer, sizeof(buffer));
+      absl::StrAppendFormat(&result, "[%s:%d] %s", PosixBasename(file), line,
+                            buffer);
+      if (data && (flags & ERR_TXT_STRING)) {
+        absl::StrAppendFormat(&result, "(%s)", data);
+      }
+      packed_error = ERR_get_error_line_data(&file, &line, &data, &flags);
+      if (packed_error != 0) {
+        absl::StrAppend(&result, ", ");
+      }
+    }
+  }
+  return result;
+}
+
 }  // namespace quic
diff --git a/quiche/quic/core/crypto/crypto_utils.h b/quiche/quic/core/crypto/crypto_utils.h
index 41823fe..31ec380 100644
--- a/quiche/quic/core/crypto/crypto_utils.h
+++ b/quiche/quic/core/crypto/crypto_utils.h
@@ -252,6 +252,10 @@
   // protocol using the certificate key.
   static std::optional<std::string> GenerateProofPayloadToBeSigned(
       absl::string_view chlo_hash, absl::string_view server_config);
+
+  // Returns the SSL error queue in a human-readable string. The error queue is
+  // cleared by the function.
+  static std::string GetSSLErrorStack();
 };
 
 }  // namespace quic
diff --git a/quiche/quic/core/crypto/crypto_utils_test.cc b/quiche/quic/core/crypto/crypto_utils_test.cc
index 707c3e2..6ddc522 100644
--- a/quiche/quic/core/crypto/crypto_utils_test.cc
+++ b/quiche/quic/core/crypto/crypto_utils_test.cc
@@ -9,7 +9,10 @@
 
 #include "absl/base/macros.h"
 #include "absl/strings/escaping.h"
+#include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
+#include "openssl/err.h"
+#include "openssl/ssl.h"
 #include "quiche/quic/core/quic_utils.h"
 #include "quiche/quic/platform/api/quic_test.h"
 #include "quiche/quic/test_tools/quic_test_utils.h"
@@ -19,6 +22,9 @@
 namespace test {
 namespace {
 
+using ::testing::AllOf;
+using ::testing::HasSubstr;
+
 class CryptoUtilsTest : public QuicTest {};
 
 TEST_F(CryptoUtilsTest, HandshakeFailureReasonToString) {
@@ -258,6 +264,16 @@
   }
 }
 
+TEST_F(CryptoUtilsTest, GetSSLErrorStack) {
+  ERR_clear_error();
+  const int line = (OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_SSL_VERSION), __LINE__);
+  std::string error_location = absl::StrCat("crypto_utils_test.cc:", line);
+  EXPECT_THAT(CryptoUtils::GetSSLErrorStack(),
+              AllOf(HasSubstr(error_location), HasSubstr("WRONG_SSL_VERSION")));
+  EXPECT_TRUE(CryptoUtils::GetSSLErrorStack().empty());
+  ERR_clear_error();
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quiche/quic/core/quic_dispatcher.cc b/quiche/quic/core/quic_dispatcher.cc
index 654e58c..0685c64 100644
--- a/quiche/quic/core/quic_dispatcher.cc
+++ b/quiche/quic/core/quic_dispatcher.cc
@@ -584,7 +584,7 @@
       uint8_t tls_alert = *extract_chlo_result.tls_alert;
       connection_close_error_code = TlsAlertToQuicErrorCode(tls_alert);
       tls_alert_error_detail =
-          absl::StrCat("TLS handshake failure (",
+          absl::StrCat("TLS handshake failure from dispatcher (",
                        EncryptionLevelToString(ENCRYPTION_INITIAL), ") ",
                        static_cast<int>(tls_alert), ": ",
                        SSL_alert_desc_string_long(tls_alert));
diff --git a/quiche/quic/core/quic_utils.cc b/quiche/quic/core/quic_utils.cc
index 5a86673..e2663f3 100644
--- a/quiche/quic/core/quic_utils.cc
+++ b/quiche/quic/core/quic_utils.cc
@@ -618,6 +618,23 @@
   return total;
 }
 
+absl::string_view PosixBasename(absl::string_view path) {
+  constexpr char kPathSeparator = '/';
+  size_t pos = path.find_last_of(kPathSeparator);
+
+  // Handle the case with no `kPathSeparator` in `path`.
+  if (pos == absl::string_view::npos) {
+    return path;
+  }
+
+  // Handle the case with a single leading `kPathSeparator` in `path`.
+  if (pos == 0) {
+    return absl::ClippedSubstr(path, 1);
+  }
+
+  return absl::ClippedSubstr(path, pos + 1);
+}
+
 std::string RawSha256(absl::string_view input) {
   std::string raw_hash;
   raw_hash.resize(SHA256_DIGEST_LENGTH);
diff --git a/quiche/quic/core/quic_utils.h b/quiche/quic/core/quic_utils.h
index 2c39781..5bd6121 100644
--- a/quiche/quic/core/quic_utils.h
+++ b/quiche/quic/core/quic_utils.h
@@ -226,6 +226,15 @@
 
 QuicByteCount MemSliceSpanTotalSize(absl::Span<quiche::QuicheMemSlice> span);
 
+// Returns the part of the path after the final "/".  If there is no "/" in the
+// path, the result is the same as the input.
+// Note that this function's behavior differs from the POSIX standard basename
+// function if path ends with "/". For such paths, this function returns the
+// empty string.
+// This function returns path as-is, if it's windows path with backslash
+// separators.
+absl::string_view PosixBasename(absl::string_view path);
+
 // Computes a SHA-256 hash and returns the raw bytes of the hash.
 QUICHE_EXPORT std::string RawSha256(absl::string_view input);
 
diff --git a/quiche/quic/core/quic_utils_test.cc b/quiche/quic/core/quic_utils_test.cc
index e29f769..55cf72e 100644
--- a/quiche/quic/core/quic_utils_test.cc
+++ b/quiche/quic/core/quic_utils_test.cc
@@ -236,6 +236,19 @@
   EXPECT_EQ(EcnCodepointToString(ECN_CE), "CE");
 }
 
+TEST_F(QuicUtilsTest, PosixBasename) {
+  EXPECT_EQ("", PosixBasename("/hello/"));
+  EXPECT_EQ("hello", PosixBasename("/hello"));
+  EXPECT_EQ("world", PosixBasename("hello/world"));
+  EXPECT_EQ("", PosixBasename("hello/"));
+  EXPECT_EQ("world", PosixBasename("world"));
+  EXPECT_EQ("", PosixBasename("/"));
+  EXPECT_EQ("", PosixBasename(""));
+  // "\\" is not treated as a path separator.
+  EXPECT_EQ("C:\\hello", PosixBasename("C:\\hello"));
+  EXPECT_EQ("world", PosixBasename("C:\\hello/world"));
+}
+
 enum class TestEnumClassBit : uint8_t {
   BIT_ZERO = 0,
   BIT_ONE,
diff --git a/quiche/quic/core/tls_chlo_extractor.cc b/quiche/quic/core/tls_chlo_extractor.cc
index 6c17b7c..996cd79 100644
--- a/quiche/quic/core/tls_chlo_extractor.cc
+++ b/quiche/quic/core/tls_chlo_extractor.cc
@@ -397,6 +397,7 @@
                                 alpn_len);
     absl::string_view alpns_payload;
     if (!alpns_reader.ReadStringPiece16(&alpns_payload)) {
+      QUIC_CODE_COUNT_N(quic_chlo_alpns_invalid, 1, 2);
       HandleUnrecoverableError("Failed to read alpns_payload");
       return;
     }
@@ -404,6 +405,7 @@
     while (!alpns_payload_reader.IsDoneReading()) {
       absl::string_view alpn_payload;
       if (!alpns_payload_reader.ReadStringPiece8(&alpn_payload)) {
+        QUIC_CODE_COUNT_N(quic_chlo_alpns_invalid, 2, 2);
         HandleUnrecoverableError("Failed to read alpn_payload");
         return;
       }
diff --git a/quiche/quic/core/tls_client_handshaker_test.cc b/quiche/quic/core/tls_client_handshaker_test.cc
index fa7a3c8..40ff7f7 100644
--- a/quiche/quic/core/tls_client_handshaker_test.cc
+++ b/quiche/quic/core/tls_client_handshaker_test.cc
@@ -33,6 +33,7 @@
 #include "quiche/common/test_tools/quiche_test_utils.h"
 
 using testing::_;
+using testing::HasSubstr;
 
 namespace quic {
 namespace test {
@@ -647,13 +648,14 @@
         return std::find(alpns.cbegin(), alpns.cend(), kTestAlpn);
       });
 
-  EXPECT_CALL(*server_connection_,
-              CloseConnection(QUIC_HANDSHAKE_FAILED,
-                              static_cast<QuicIetfTransportErrorCodes>(
-                                  CRYPTO_ERROR_FIRST + 120),
-                              "TLS handshake failure (ENCRYPTION_INITIAL) 120: "
-                              "no application protocol",
-                              _));
+  EXPECT_CALL(
+      *server_connection_,
+      CloseConnection(
+          QUIC_HANDSHAKE_FAILED,
+          static_cast<QuicIetfTransportErrorCodes>(CRYPTO_ERROR_FIRST + 120),
+          HasSubstr("TLS handshake failure (ENCRYPTION_INITIAL) 120: "
+                    "no application protocol"),
+          _));
 
   stream()->CryptoConnect();
   crypto_test_utils::AdvanceHandshake(connection_, stream(), 0,
diff --git a/quiche/quic/core/tls_handshaker.cc b/quiche/quic/core/tls_handshaker.cc
index ff9bfe5..dffb4d9 100644
--- a/quiche/quic/core/tls_handshaker.cc
+++ b/quiche/quic/core/tls_handshaker.cc
@@ -154,22 +154,31 @@
   }
   if (ShouldCloseConnectionOnUnexpectedError(ssl_error) &&
       !is_connection_closed()) {
+    std::string ssl_error_stack;
+    if (GetQuicReloadableFlag(quic_add_ssl_error_stack_to_error_detail)) {
+      QUIC_RELOADABLE_FLAG_COUNT(quic_add_ssl_error_stack_to_error_detail);
+      ssl_error_stack = CryptoUtils::GetSSLErrorStack();
+    } else {
+      ERR_print_errors_fp(stderr);
+    }
     QUIC_VLOG(1) << "SSL_do_handshake failed; SSL_get_error returns "
-                 << ssl_error;
-    ERR_print_errors_fp(stderr);
+                 << ssl_error << ", SSLErrorStack: " << ssl_error_stack;
     if (last_tls_alert_.has_value()) {
       std::string error_details =
           absl::StrCat("TLS handshake failure (",
                        EncryptionLevelToString(last_tls_alert_->level), ") ",
                        static_cast<int>(last_tls_alert_->desc), ": ",
-                       SSL_alert_desc_string_long(last_tls_alert_->desc));
+                       SSL_alert_desc_string_long(last_tls_alert_->desc),
+                       ". SSLErrorStack:", ssl_error_stack);
       QUIC_DLOG(ERROR) << error_details;
       CloseConnection(TlsAlertToQuicErrorCode(last_tls_alert_->desc),
                       static_cast<QuicIetfTransportErrorCodes>(
                           CRYPTO_ERROR_FIRST + last_tls_alert_->desc),
                       error_details);
     } else {
-      CloseConnection(QUIC_HANDSHAKE_FAILED, "TLS handshake failed");
+      CloseConnection(QUIC_HANDSHAKE_FAILED,
+                      absl::StrCat("TLS handshake failed. SSLErrorStack:",
+                                   ssl_error_stack));
     }
   }
 }
diff --git a/quiche/quic/core/tls_server_handshaker_test.cc b/quiche/quic/core/tls_server_handshaker_test.cc
index b76ea32..319832e 100644
--- a/quiche/quic/core/tls_server_handshaker_test.cc
+++ b/quiche/quic/core/tls_server_handshaker_test.cc
@@ -42,6 +42,7 @@
 }  // namespace quic
 
 using testing::_;
+using testing::HasSubstr;
 using testing::NiceMock;
 using testing::Return;
 
@@ -698,13 +699,14 @@
   EXPECT_CALL(*client_session_, GetAlpnsToOffer())
       .WillOnce(Return(std::vector<std::string>({kTestBadClientAlpn})));
 
-  EXPECT_CALL(*server_connection_,
-              CloseConnection(QUIC_HANDSHAKE_FAILED,
-                              static_cast<QuicIetfTransportErrorCodes>(
-                                  CRYPTO_ERROR_FIRST + 120),
-                              "TLS handshake failure (ENCRYPTION_INITIAL) 120: "
-                              "no application protocol",
-                              _));
+  EXPECT_CALL(
+      *server_connection_,
+      CloseConnection(
+          QUIC_HANDSHAKE_FAILED,
+          static_cast<QuicIetfTransportErrorCodes>(CRYPTO_ERROR_FIRST + 120),
+          HasSubstr("TLS handshake failure (ENCRYPTION_INITIAL) 120: "
+                    "no application protocol"),
+          _));
 
   AdvanceHandshakeWithFakeClient();