In QUIC, add error code QUIC_SILENT_IDLE_TIMEOUT. Used when server silently close connection due to no network activity. The connection close packets would be serialized and will be sent by time wait list upon receiving client packets. Protected by gfe2_reloadable_flag_quic_add_silent_idle_timeout.

PiperOrigin-RevId: 325226891
Change-Id: Id32192da063e5f4f605ef4afcb779c6b7e6a700d
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index 16d7e5c..5f645bd 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -514,6 +514,11 @@
                        config.IdleNetworkTimeout());
     idle_timeout_connection_close_behavior_ =
         ConnectionCloseBehavior::SILENT_CLOSE;
+    if (GetQuicReloadableFlag(quic_add_silent_idle_timeout) &&
+        perspective_ == Perspective::IS_SERVER) {
+      idle_timeout_connection_close_behavior_ = ConnectionCloseBehavior::
+          SILENT_CLOSE_WITH_CONNECTION_CLOSE_PACKET_SERIALIZED;
+    }
     if (!ValidateConfigConnectionIds(config)) {
       return;
     }
@@ -2573,7 +2578,8 @@
           ? packet->fate
           : GetSerializedPacketFate(is_mtu_discovery, packet->encryption_level);
   // Termination packets are encrypted and saved, so don't exit early.
-  const bool is_termination_packet = IsTerminationPacket(*packet);
+  QuicErrorCode error_code = QUIC_NO_ERROR;
+  const bool is_termination_packet = IsTerminationPacket(*packet, &error_code);
   QuicPacketNumber packet_number = packet->packet_number;
   QuicPacketLength encrypted_length = packet->encrypted_length;
   // Termination packets are eventually owned by TimeWaitListManager.
@@ -2587,6 +2593,17 @@
     char* buffer_copy = CopyBuffer(*packet);
     termination_packets_->emplace_back(
         new QuicEncryptedPacket(buffer_copy, encrypted_length, true));
+    if (error_code == QUIC_SILENT_IDLE_TIMEOUT) {
+      QUIC_RELOADABLE_FLAG_COUNT(quic_add_silent_idle_timeout);
+      DCHECK_EQ(Perspective::IS_SERVER, perspective_);
+      // TODO(fayang): populate histogram indicating the time elapsed from this
+      // connection gets closed to following client packets get received.
+      QUIC_DVLOG(1) << ENDPOINT
+                    << "Added silent connection close to termination packets, "
+                       "num of termination packets: "
+                    << termination_packets_->size();
+      return true;
+    }
   }
 
   DCHECK_LE(encrypted_length, kMaxOutgoingPacketSize);
@@ -3772,12 +3789,14 @@
   }
 }
 
-bool QuicConnection::IsTerminationPacket(const SerializedPacket& packet) {
+bool QuicConnection::IsTerminationPacket(const SerializedPacket& packet,
+                                         QuicErrorCode* error_code) {
   if (packet.retransmittable_frames.empty()) {
     return false;
   }
   for (const QuicFrame& frame : packet.retransmittable_frames) {
     if (frame.type == CONNECTION_CLOSE_FRAME) {
+      *error_code = frame.connection_close_frame->quic_error_code;
       return true;
     }
   }
@@ -4715,7 +4734,12 @@
                     ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
     return;
   }
-  CloseConnection(QUIC_NETWORK_IDLE_TIMEOUT, error_details,
+  QuicErrorCode error_code = QUIC_NETWORK_IDLE_TIMEOUT;
+  if (GetQuicReloadableFlag(quic_add_silent_idle_timeout) &&
+      perspective_ == Perspective::IS_SERVER) {
+    error_code = QUIC_SILENT_IDLE_TIMEOUT;
+  }
+  CloseConnection(error_code, error_details,
                   idle_timeout_connection_close_behavior_);
 }
 
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index 50c6496..22693d2 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -1188,7 +1188,8 @@
   void MaybeSetMtuAlarm(QuicPacketNumber sent_packet_number);
 
   HasRetransmittableData IsRetransmittable(const SerializedPacket& packet);
-  bool IsTerminationPacket(const SerializedPacket& packet);
+  bool IsTerminationPacket(const SerializedPacket& packet,
+                           QuicErrorCode* error_code);
 
   // Set the size of the packet we are targeting while doing path MTU discovery.
   void SetMtuDiscoveryTarget(QuicByteCount target);
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 4d3189d..b39f606 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -11580,6 +11580,43 @@
   EXPECT_EQ(deadline, connection_.GetTimeoutAlarm()->deadline());
 }
 
+TEST_P(QuicConnectionTest, SilentIdleTimeout) {
+  set_perspective(Perspective::IS_SERVER);
+  QuicPacketCreatorPeer::SetSendVersionInPacket(creator_, false);
+  if (version().SupportsAntiAmplificationLimit()) {
+    QuicConnectionPeer::SetAddressValidated(&connection_);
+  }
+
+  QuicConfig config;
+  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));
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
+  connection_.GetTimeoutAlarm()->Fire();
+  if (GetQuicReloadableFlag(quic_add_silent_idle_timeout)) {
+    // Verify the connection close packets get serialized and added to
+    // termination packets list.
+    EXPECT_NE(nullptr,
+              QuicConnectionPeer::GetConnectionClosePacket(&connection_));
+  } else {
+    // Verify no connection close packet is serialized.
+    EXPECT_EQ(nullptr,
+              QuicConnectionPeer::GetConnectionClosePacket(&connection_));
+  }
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_error_codes.cc b/quic/core/quic_error_codes.cc
index 7065363..cf153e3 100644
--- a/quic/core/quic_error_codes.cc
+++ b/quic/core/quic_error_codes.cc
@@ -228,6 +228,7 @@
     RETURN_STRING_LITERAL(QUIC_ZERO_RTT_UNRETRANSMITTABLE);
     RETURN_STRING_LITERAL(QUIC_ZERO_RTT_REJECTION_LIMIT_REDUCED);
     RETURN_STRING_LITERAL(QUIC_ZERO_RTT_RESUMPTION_LIMIT_REDUCED);
+    RETURN_STRING_LITERAL(QUIC_SILENT_IDLE_TIMEOUT);
 
     RETURN_STRING_LITERAL(QUIC_LAST_ERROR);
     // Intentionally have no default case, so we'll break the build
@@ -362,6 +363,8 @@
       return {true, static_cast<uint64_t>(PROTOCOL_VIOLATION)};
     case QUIC_NETWORK_IDLE_TIMEOUT:
       return {true, static_cast<uint64_t>(NO_IETF_QUIC_ERROR)};
+    case QUIC_SILENT_IDLE_TIMEOUT:
+      return {true, static_cast<uint64_t>(NO_IETF_QUIC_ERROR)};
     case QUIC_HANDSHAKE_TIMEOUT:
       return {true, static_cast<uint64_t>(NO_IETF_QUIC_ERROR)};
     case QUIC_ERROR_MIGRATING_ADDRESS:
diff --git a/quic/core/quic_error_codes.h b/quic/core/quic_error_codes.h
index 0c990c7..b57eaa7 100644
--- a/quic/core/quic_error_codes.h
+++ b/quic/core/quic_error_codes.h
@@ -489,8 +489,11 @@
   // This is the peer violating QUIC spec.
   QUIC_ZERO_RTT_RESUMPTION_LIMIT_REDUCED = 163,
 
+  // The connection silently timed out due to no network activity.
+  QUIC_SILENT_IDLE_TIMEOUT = 168,
+
   // No error. Used as bound while iterating.
-  QUIC_LAST_ERROR = 168,
+  QUIC_LAST_ERROR = 169,
 };
 // 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/quic/core/quic_types.h b/quic/core/quic_types.h
index 3818dad..55c5046 100644
--- a/quic/core/quic_types.h
+++ b/quic/core/quic_types.h
@@ -211,6 +211,7 @@
 // Should a connection be closed silently or not.
 enum class ConnectionCloseBehavior {
   SILENT_CLOSE,
+  SILENT_CLOSE_WITH_CONNECTION_CLOSE_PACKET_SERIALIZED,
   SEND_CONNECTION_CLOSE_PACKET
 };