Add a connection option (`kBSUS`) to force the server to buffer incoming streams until the SETTINGS frame is received.

The intention is to run an experiment to see if this behavior causes any performance regressions, since we rely on it for WebTransport.

Protected by quic_reloadable_flag_quic_block_until_settings_received_copt.

PiperOrigin-RevId: 546743279
diff --git a/quiche/quic/core/crypto/crypto_protocol.h b/quiche/quic/core/crypto/crypto_protocol.h
index 7a79e62..2725912 100644
--- a/quiche/quic/core/crypto/crypto_protocol.h
+++ b/quiche/quic/core/crypto/crypto_protocol.h
@@ -465,6 +465,10 @@
 const QuicTag kMCS2 = TAG('M', 'C', 'S', '2');
 const QuicTag kMCS3 = TAG('M', 'C', 'S', '3');
 
+constexpr QuicTag kBSUS = TAG('B', 'S', 'U', 'S');  // Blocks server connection
+                                                    // until the SETTINGS frame
+                                                    // is received.
+
 // clang-format on
 
 // These tags have a special form so that they appear either at the beginning
diff --git a/quiche/quic/core/http/end_to_end_test.cc b/quiche/quic/core/http/end_to_end_test.cc
index f8028ea..f160861 100644
--- a/quiche/quic/core/http/end_to_end_test.cc
+++ b/quiche/quic/core/http/end_to_end_test.cc
@@ -6457,6 +6457,26 @@
   ADD_FAILURE() << "Client should not have 10 resumption tickets.";
 }
 
+TEST_P(EndToEndTest, BlockServerUntilSettingsReceived) {
+  SetQuicReloadableFlag(quic_block_until_settings_received_copt, true);
+  // Force loss to test data stream being blocked when SETTINGS are missing.
+  SetPacketLossPercentage(30);
+  client_extra_copts_.push_back(kBSUS);
+  ASSERT_TRUE(Initialize());
+
+  if (!version_.UsesHttp3()) {
+    return;
+  }
+
+  SendSynchronousFooRequestAndCheckResponse();
+
+  QuicSpdySession* server_session = GetServerSession();
+  EXPECT_FALSE(GetClientSession()->ShouldBufferRequestsUntilSettings());
+  server_thread_->ScheduleAndWaitForCompletion([server_session] {
+    EXPECT_TRUE(server_session->ShouldBufferRequestsUntilSettings());
+  });
+}
+
 TEST_P(EndToEndTest, WebTransportSessionSetup) {
   enable_web_transport_ = true;
   ASSERT_TRUE(Initialize());
diff --git a/quiche/quic/core/http/quic_spdy_session.cc b/quiche/quic/core/http/quic_spdy_session.cc
index 44fff6c..ee6d6d1 100644
--- a/quiche/quic/core/http/quic_spdy_session.cc
+++ b/quiche/quic/core/http/quic_spdy_session.cc
@@ -469,7 +469,8 @@
       debug_visitor_(nullptr),
       destruction_indicator_(123456789),
       allow_extended_connect_(perspective() == Perspective::IS_SERVER &&
-                              VersionUsesHttp3(transport_version())) {
+                              VersionUsesHttp3(transport_version())),
+      force_buffer_requests_until_settings_(false) {
   h2_deframer_.set_visitor(spdy_framer_visitor_.get());
   h2_deframer_.set_debug_visitor(spdy_framer_visitor_.get());
   spdy_framer_.set_debug_visitor(spdy_framer_visitor_.get());
@@ -1052,6 +1053,8 @@
   QUICHE_DCHECK(!settings_received_);
   settings_received_ = true;
   for (QuicStreamId stream_id : streams_waiting_for_settings_) {
+    QUICHE_RELOADABLE_FLAG_COUNT_N(quic_block_until_settings_received_copt, 4,
+                                   4);
     QUICHE_DCHECK(ShouldBufferRequestsUntilSettings());
     QuicSpdyStream* stream = GetOrCreateSpdyDataStream(stream_id);
     if (stream == nullptr) {
@@ -1812,12 +1815,14 @@
     return true;
   }
 
+  QUICHE_RELOADABLE_FLAG_COUNT_N(quic_block_until_settings_received_copt, 2, 4);
   return settings_received_;
 }
 
 void QuicSpdySession::OnStreamWaitingForClientSettings(QuicStreamId id) {
   QUICHE_DCHECK(ShouldBufferRequestsUntilSettings());
   QUICHE_DCHECK(QuicUtils::IsBidirectionalStreamId(id, version()));
+  QUICHE_RELOADABLE_FLAG_COUNT_N(quic_block_until_settings_received_copt, 3, 4);
   streams_waiting_for_settings_.insert(id);
 }
 
@@ -1960,6 +1965,18 @@
   allow_extended_connect_ = allow_extended_connect;
 }
 
+void QuicSpdySession::OnConfigNegotiated() {
+  QuicSession::OnConfigNegotiated();
+
+  if (GetQuicReloadableFlag(quic_block_until_settings_received_copt) &&
+      perspective() == Perspective::IS_SERVER &&
+      config()->HasClientSentConnectionOption(kBSUS, Perspective::IS_SERVER)) {
+    QUICHE_RELOADABLE_FLAG_COUNT_N(quic_block_until_settings_received_copt, 1,
+                                   4);
+    force_buffer_requests_until_settings_ = true;
+  }
+}
+
 #undef ENDPOINT  // undef for jumbo builds
 
 }  // namespace quic
diff --git a/quiche/quic/core/http/quic_spdy_session.h b/quiche/quic/core/http/quic_spdy_session.h
index 627e226..98e4aba 100644
--- a/quiche/quic/core/http/quic_spdy_session.h
+++ b/quiche/quic/core/http/quic_spdy_session.h
@@ -441,7 +441,8 @@
   bool ShouldBufferRequestsUntilSettings() {
     return version().UsesHttp3() && perspective() == Perspective::IS_SERVER &&
            (ShouldNegotiateWebTransport() ||
-            LocalHttpDatagramSupport() == HttpDatagramSupport::kRfcAndDraft04);
+            LocalHttpDatagramSupport() == HttpDatagramSupport::kRfcAndDraft04 ||
+            force_buffer_requests_until_settings_);
   }
 
   // Returns if the incoming bidirectional streams should process data.  This is
@@ -479,6 +480,8 @@
 
   QuicSpdyStream* GetOrCreateSpdyDataStream(const QuicStreamId stream_id);
 
+  void OnConfigNegotiated() override;
+
  protected:
   // Override CreateIncomingStream(), CreateOutgoingBidirectionalStream() and
   // CreateOutgoingUnidirectionalStream() with QuicSpdyStream return type to
@@ -705,6 +708,10 @@
   // server cannot initiate WebTransport sessions.
   absl::flat_hash_map<WebTransportHttp3Version, QuicStreamCount>
       max_webtransport_sessions_;
+
+  // Allows forcing ShouldBufferRequestsUntilSettings() to true via
+  // a connection option.
+  bool force_buffer_requests_until_settings_;
 };
 
 }  // namespace quic
diff --git a/quiche/quic/core/quic_flags_list.h b/quiche/quic/core/quic_flags_list.h
index 857fea7..072b449 100644
--- a/quiche/quic/core/quic_flags_list.h
+++ b/quiche/quic/core/quic_flags_list.h
@@ -15,6 +15,8 @@
 QUIC_FLAG(quic_restart_flag_quic_testonly_default_false, false)
 // A testonly restart flag that will always default to true.
 QUIC_FLAG(quic_restart_flag_quic_testonly_default_true, true)
+// If enabled and a BSUS connection is received, blocks server connections until SETTINGS frame is received.
+QUIC_FLAG(quic_reloadable_flag_quic_block_until_settings_received_copt, false)
 // If trrue, early return before write control frame in OnCanWrite() if the connection is already closed.
 QUIC_FLAG(quic_reloadable_flag_quic_no_write_control_frame_upon_connection_close, true)
 // If true, QUIC BBR2 will ignore non-positive RTT samples.