Add connection option NSLC to always send connection close for idle timeout. Protected by gfe2_reloadable_flag_quic_no_silent_close_for_idle_timeout.

PiperOrigin-RevId: 325443767
Change-Id: I681597b69c8bc256a9167c5dc0169217f41373e8
diff --git a/quic/core/crypto/crypto_protocol.h b/quic/core/crypto/crypto_protocol.h
index ca972c1..8d907d8 100644
--- a/quic/core/crypto/crypto_protocol.h
+++ b/quic/core/crypto/crypto_protocol.h
@@ -307,6 +307,9 @@
                                                 // has the highest priority.
 const QuicTag kRRWS = TAG('R', 'R', 'W', 'S');  // Round robin write scheduling.
 
+const QuicTag kNSLC = TAG('N', 'S', 'L', 'C');  // Always send connection close
+                                                // for idle timeout.
+
 // Proof types (i.e. certificate types)
 // NOTE: although it would be silly to do so, specifying both kX509 and kX59R
 // is allowed and is equivalent to specifying only kX509.
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index 5f645bd..f442b1b 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -519,6 +519,12 @@
       idle_timeout_connection_close_behavior_ = ConnectionCloseBehavior::
           SILENT_CLOSE_WITH_CONNECTION_CLOSE_PACKET_SERIALIZED;
     }
+    if (GetQuicReloadableFlag(quic_no_silent_close_for_idle_timeout) &&
+        config.HasClientRequestedIndependentOption(kNSLC, perspective_)) {
+      QUIC_RELOADABLE_FLAG_COUNT(quic_no_silent_close_for_idle_timeout);
+      idle_timeout_connection_close_behavior_ =
+          ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET;
+    }
     if (!ValidateConfigConnectionIds(config)) {
       return;
     }
@@ -4735,8 +4741,9 @@
     return;
   }
   QuicErrorCode error_code = QUIC_NETWORK_IDLE_TIMEOUT;
-  if (GetQuicReloadableFlag(quic_add_silent_idle_timeout) &&
-      perspective_ == Perspective::IS_SERVER) {
+  if (idle_timeout_connection_close_behavior_ ==
+      ConnectionCloseBehavior::
+          SILENT_CLOSE_WITH_CONNECTION_CLOSE_PACKET_SERIALIZED) {
     error_code = QUIC_SILENT_IDLE_TIMEOUT;
   }
   CloseConnection(error_code, error_details,
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index b39f606..c547d19 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -11617,6 +11617,42 @@
   }
 }
 
+TEST_P(QuicConnectionTest, NoSilentClose) {
+  set_perspective(Perspective::IS_SERVER);
+  QuicPacketCreatorPeer::SetSendVersionInPacket(creator_, false);
+  if (version().SupportsAntiAmplificationLimit()) {
+    QuicConnectionPeer::SetAddressValidated(&connection_);
+  }
+
+  QuicConfig config;
+  QuicTagVector connection_options;
+  connection_options.push_back(kNSLC);
+  config.SetInitialReceivedConnectionOptions(connection_options);
+  QuicConfigPeer::SetNegotiated(&config, true);
+  if (connection_.version().AuthenticatesHandshakeConnectionIds()) {
+    QuicConfigPeer::SetReceivedOriginalConnectionId(
+        &config, connection_.connection_id());
+    QuicConfigPeer::SetReceivedInitialSourceConnectionId(&config,
+                                                         QuicConnectionId());
+  }
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+  connection_.SetFromConfig(config);
+
+  EXPECT_TRUE(connection_.connected());
+  EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
+
+  EXPECT_CALL(visitor_,
+              OnConnectionClosed(_, ConnectionCloseSource::FROM_SELF));
+  connection_.GetTimeoutAlarm()->Fire();
+  if (GetQuicReloadableFlag(quic_no_silent_close_for_idle_timeout)) {
+    TestConnectionCloseQuicErrorCode(QUIC_NETWORK_IDLE_TIMEOUT);
+  } else {
+    // Verify no connection close packet is serialized.
+    EXPECT_EQ(nullptr,
+              QuicConnectionPeer::GetConnectionClosePacket(&connection_));
+  }
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic