Avoid nested CloseConnection calls in QuicConnection.
Protected by quic_reloadable_flag_quic_avoid_nested_close_connection.
PiperOrigin-RevId: 689062814
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h
index 4285e06..3cf6f59 100755
--- a/quiche/common/quiche_feature_flags_list.h
+++ b/quiche/common/quiche_feature_flags_list.h
@@ -14,6 +14,7 @@
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, true, true, "If true, requests with a host header will be allowed.")
+QUICHE_FLAG(bool, quiche_reloadable_flag_quic_avoid_nested_close_connection, false, true, "If true, if QuicConnection::CloseConnection triggers a nested call to itself, make the nested call a no-op.")
QUICHE_FLAG(bool, quiche_reloadable_flag_quic_bbr2_extra_acked_window, false, true, "When true, the BBR4 copt sets the extra_acked window to 20 RTTs and BBR5 sets it to 40 RTTs.")
QUICHE_FLAG(bool, quiche_reloadable_flag_quic_bbr2_probe_two_rounds, true, true, "When true, the BB2U copt causes BBR2 to wait two rounds with out draining the queue before exiting PROBE_UP and BB2S has the same effect in STARTUP.")
QUICHE_FLAG(bool, quiche_reloadable_flag_quic_bbr2_simplify_inflight_hi, true, true, "When true, the BBHI copt causes QUIC BBRv2 to use a simpler algorithm for raising inflight_hi in PROBE_UP.")
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index 274e1b7..843ef84 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -22,6 +22,7 @@
#include <utility>
#include <vector>
+#include "absl/cleanup/cleanup.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
@@ -4559,6 +4560,17 @@
return;
}
+ if (in_close_connection_) {
+ QUIC_DLOG(INFO) << "Connection is being closed.";
+ return;
+ }
+
+ if (GetQuicReloadableFlag(quic_avoid_nested_close_connection)) {
+ QUIC_RELOADABLE_FLAG_COUNT(quic_avoid_nested_close_connection);
+ in_close_connection_ = true;
+ }
+ absl::Cleanup cleanup = [this]() { in_close_connection_ = false; };
+
if (ietf_error != NO_IETF_QUIC_ERROR) {
QUIC_DLOG(INFO) << ENDPOINT << "Closing connection: " << connection_id()
<< ", with wire error: " << ietf_error
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index e09fa3d..cbb587e 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -2349,6 +2349,9 @@
// close.
bool connected_;
+ // True if the connection is in the CloseConnection stack.
+ bool in_close_connection_ = false;
+
// Set to false if the connection should not send truncated connection IDs to
// the peer, even if the peer supports it.
bool can_truncate_connection_ids_;
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index 6aa7ef3..ae219f3 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -1596,6 +1596,41 @@
::testing::ValuesIn(GetTestParams()),
::testing::PrintToStringParamName());
+// Regression test for b/372756997.
+TEST_P(QuicConnectionTest, NoNestedCloseConnection) {
+ if (!GetQuicReloadableFlag(quic_avoid_nested_close_connection)) {
+ // EXPECT_QUIC_BUG tests are expensive so only run one instance of them.
+ if (!IsDefaultTestConfiguration()) {
+ return;
+ }
+ }
+ EXPECT_TRUE(connection_.connected());
+ EXPECT_CALL(visitor_, OnConnectionClosed(_, ConnectionCloseSource::FROM_SELF))
+ .WillRepeatedly(
+ Invoke(this, &QuicConnectionTest::SaveConnectionCloseFrame));
+ EXPECT_CALL(connection_, OnSerializedPacket(_)).Times(AnyNumber());
+
+ // Prepare the writer to fail to send the first connection close packet due
+ // to the packet being too large.
+ writer_->SetShouldWriteFail();
+ writer_->SetWriteError(*writer_->MessageTooBigErrorCode());
+
+ if (GetQuicReloadableFlag(quic_avoid_nested_close_connection)) {
+ connection_.CloseConnection(
+ QUIC_CRYPTO_TOO_MANY_ENTRIES, "Closed by test",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ EXPECT_THAT(saved_connection_close_frame_.quic_error_code,
+ IsError(QUIC_CRYPTO_TOO_MANY_ENTRIES));
+ } else {
+ EXPECT_QUIC_BUG(
+ connection_.CloseConnection(
+ QUIC_CRYPTO_TOO_MANY_ENTRIES, "Closed by test",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET),
+ // 30=QUIC_CRYPTO_TOO_MANY_ENTRIES, 27=QUIC_PACKET_WRITE_ERROR.
+ "Initial error code: 30, new error code: 27");
+ }
+}
+
// These two tests ensure that the QuicErrorCode mapping works correctly.
// Both tests expect to see a Google QUIC close if not running IETF QUIC.
// If running IETF QUIC, the first will generate a transport connection