If QUIC server closes connection due to invalid hostname in SNI, use new connection close code `QUIC_HANDSHAKE_FAILED_INVALID_HOSTNAME` instead of the generic `QUIC_HANDSHAKE_FAILED`.

Protected by FLAGS_quic_reloadable_flag_quic_new_error_code_for_invalid_hostname (Default true).

PiperOrigin-RevId: 678914877
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h
index 7b24907..7528191 100755
--- a/quiche/common/quiche_feature_flags_list.h
+++ b/quiche/common/quiche_feature_flags_list.h
@@ -39,6 +39,7 @@
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_gfe_allow_alps_new_codepoint, true, true, "If true, allow quic to use new ALPS codepoint to negotiate during handshake for H3 if client sends new ALPS codepoint.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_ignore_gquic_probing, true, true, "If true, QUIC server will not respond to gQUIC probing packet(PING + PADDING) but treat it as a regular packet.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_limit_new_streams_per_loop_2, true, true, "If true, when the peer sends connection options \\\'SLP1\\\', \\\'SLP2\\\' and \\\'SLPF\\\', internet facing GFEs will only allow a limited number of new requests to be processed per event loop, and postpone the rest to the following event loops. Also guard QuicConnection to iterate through all decrypters at each encryption level to get cipher id for a request.")
+QUICHE_FLAG(bool, quiche_reloadable_flag_quic_new_error_code_for_invalid_hostname, true, true, "If true, TlsServerHandshaker will close connection with QUIC_HANDSHAKE_FAILED_INVALID_HOSTNAME, instead of QUIC_HANDSHAKE_FAILED, when the hostname in ClientHello is invalid.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_new_error_code_when_packets_buffered_too_long, true, true, "If true, dispatcher sends error code QUIC_HANDSHAKE_FAILED_PACKETS_BUFFERED_TOO_LONG when handshake fails due to packets buffered for too long.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_no_path_degrading_before_handshake_confirmed, true, true, "If true, an endpoint does not detect path degrading or blackholing until handshake gets confirmed.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_no_write_control_frame_upon_connection_close, false, true, "If trrue, early return before write control frame in OnCanWrite() if the connection is already closed.")
diff --git a/quiche/quic/core/http/end_to_end_test.cc b/quiche/quic/core/http/end_to_end_test.cc
index 671eff1..452586c 100644
--- a/quiche/quic/core/http/end_to_end_test.cc
+++ b/quiche/quic/core/http/end_to_end_test.cc
@@ -1195,9 +1195,15 @@
 
   QuicSpdySession* client_session = GetClientSession();
   ASSERT_TRUE(client_session);
-  EXPECT_THAT(client_session->error(), IsError(QUIC_HANDSHAKE_FAILED));
-  EXPECT_THAT(client_session->error_details(),
-              HasSubstr("select_cert_error: invalid hostname"));
+  if (GetQuicReloadableFlag(quic_new_error_code_for_invalid_hostname)) {
+    EXPECT_THAT(client_session->error(),
+                IsError(QUIC_HANDSHAKE_FAILED_INVALID_HOSTNAME));
+    EXPECT_THAT(client_session->error_details(), HasSubstr("invalid hostname"));
+  } else {
+    EXPECT_THAT(client_session->error(), IsError(QUIC_HANDSHAKE_FAILED));
+    EXPECT_THAT(client_session->error_details(),
+                HasSubstr("select_cert_error: invalid hostname"));
+  }
 }
 
 // Two packet CHLO. The first one is buffered and acked by dispatcher, the
diff --git a/quiche/quic/core/quic_error_codes.cc b/quiche/quic/core/quic_error_codes.cc
index daf62b4..b5bee26 100644
--- a/quiche/quic/core/quic_error_codes.cc
+++ b/quiche/quic/core/quic_error_codes.cc
@@ -97,6 +97,7 @@
     RETURN_STRING_LITERAL(QUIC_PEER_GOING_AWAY);
     RETURN_STRING_LITERAL(QUIC_HANDSHAKE_FAILED);
     RETURN_STRING_LITERAL(QUIC_HANDSHAKE_FAILED_PACKETS_BUFFERED_TOO_LONG);
+    RETURN_STRING_LITERAL(QUIC_HANDSHAKE_FAILED_INVALID_HOSTNAME);
     RETURN_STRING_LITERAL(QUIC_CRYPTO_TAGS_OUT_OF_ORDER);
     RETURN_STRING_LITERAL(QUIC_CRYPTO_TOO_MANY_ENTRIES);
     RETURN_STRING_LITERAL(QUIC_CRYPTO_TOO_MANY_REJECTS);
@@ -802,6 +803,8 @@
       return {true, static_cast<uint64_t>(NO_IETF_QUIC_ERROR)};
     case QUIC_CLIENT_LOST_NETWORK_ACCESS:
       return {true, static_cast<uint64_t>(NO_IETF_QUIC_ERROR)};
+    case QUIC_HANDSHAKE_FAILED_INVALID_HOSTNAME:
+      return {true, static_cast<uint64_t>(NO_IETF_QUIC_ERROR)};
     case QUIC_LAST_ERROR:
       return {false, static_cast<uint64_t>(QUIC_LAST_ERROR)};
   }
diff --git a/quiche/quic/core/quic_error_codes.h b/quiche/quic/core/quic_error_codes.h
index a28d595..19c806a 100644
--- a/quiche/quic/core/quic_error_codes.h
+++ b/quiche/quic/core/quic_error_codes.h
@@ -252,6 +252,9 @@
   // Split from QUIC_HANDSHAKE_FAILED and specially indicates handshake failure
   // due to packets buffered for too long.
   QUIC_HANDSHAKE_FAILED_PACKETS_BUFFERED_TOO_LONG = 214,
+  // Handshake failed due to invalid hostname in ClientHello. Only sent from
+  // server.
+  QUIC_HANDSHAKE_FAILED_INVALID_HOSTNAME = 216,
   // Handshake message contained out of order tags.
   QUIC_CRYPTO_TAGS_OUT_OF_ORDER = 29,
   // Handshake message contained too many entries.
@@ -626,7 +629,7 @@
   QUIC_CLIENT_LOST_NETWORK_ACCESS = 215,
 
   // No error. Used as bound while iterating.
-  QUIC_LAST_ERROR = 216,
+  QUIC_LAST_ERROR = 217,
 };
 // QuicErrorCodes is encoded as four octets on-the-wire when doing Google QUIC,
 // or a varint62 when doing IETF QUIC. Ensure that its value does not exceed
diff --git a/quiche/quic/core/tls_server_handshaker.cc b/quiche/quic/core/tls_server_handshaker.cc
index 705987e..0ef6e61 100644
--- a/quiche/quic/core/tls_server_handshaker.cc
+++ b/quiche/quic/core/tls_server_handshaker.cc
@@ -960,7 +960,13 @@
     crypto_negotiated_params_->sni =
         QuicHostnameUtils::NormalizeHostname(hostname);
     if (!ValidateHostname(hostname)) {
-      set_extra_error_details("select_cert_error: invalid hostname");
+      if (GetQuicReloadableFlag(quic_new_error_code_for_invalid_hostname)) {
+        QUIC_RELOADABLE_FLAG_COUNT(quic_new_error_code_for_invalid_hostname);
+        CloseConnection(QUIC_HANDSHAKE_FAILED_INVALID_HOSTNAME,
+                        "invalid hostname");
+      } else {
+        set_extra_error_details("select_cert_error: invalid hostname");
+      }
       return ssl_select_cert_error;
     }
     if (hostname != crypto_negotiated_params_->sni) {