gfe-relnote: In QUIC, use IdleNetworkDetector to detect handshake and idle network timeout. Protected by gfe2_reloadable_flag_quic_use_idle_network_detector.

PiperOrigin-RevId: 303103349
Change-Id: I3e5d6c3a2585284a69fa680b698ea14e0c86174e
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index dcb8ef8..f83dbdd 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -333,7 +333,11 @@
       bytes_received_before_address_validation_(0),
       bytes_sent_before_address_validation_(0),
       address_validated_(false),
-      blackhole_detector_(this, &arena_, alarm_factory_) {
+      blackhole_detector_(this, &arena_, alarm_factory_),
+      idle_network_detector_(this,
+                             clock_->ApproximateNow(),
+                             &arena_,
+                             alarm_factory_) {
   QUIC_DLOG(INFO) << ENDPOINT << "Created connection with server connection ID "
                   << server_connection_id
                   << " and version: " << ParsedQuicVersionToString(version());
@@ -875,7 +879,7 @@
   // frames, since the processing may result in sending a bundled ack.
   uber_received_packet_manager_.RecordPacketReceived(
       last_decrypted_packet_level_, last_header_,
-      time_of_last_received_packet_);
+      GetTimeOfLastReceivedPacket());
   DCHECK(connected_);
   return true;
 }
@@ -976,7 +980,7 @@
   }
   processing_ack_frame_ = true;
   sent_packet_manager_.OnAckFrameStart(largest_acked, ack_delay_time,
-                                       time_of_last_received_packet_);
+                                       GetTimeOfLastReceivedPacket());
   return true;
 }
 
@@ -1022,7 +1026,7 @@
   const bool one_rtt_packet_was_acked =
       sent_packet_manager_.one_rtt_packet_acked();
   const AckResult ack_result = sent_packet_manager_.OnAckFrameEnd(
-      time_of_last_received_packet_, last_header_.packet_number,
+      GetTimeOfLastReceivedPacket(), last_header_.packet_number,
       last_decrypted_packet_level_);
   if (ack_result != PACKETS_NEWLY_ACKED &&
       ack_result != NO_PACKETS_NEWLY_ACKED) {
@@ -1298,7 +1302,7 @@
   UpdatePacketContent(NOT_PADDED_PING);
 
   if (debug_visitor_ != nullptr) {
-    debug_visitor_->OnWindowUpdateFrame(frame, time_of_last_received_packet_);
+    debug_visitor_->OnWindowUpdateFrame(frame, GetTimeOfLastReceivedPacket());
   }
   QUIC_DVLOG(1) << ENDPOINT << "WINDOW_UPDATE_FRAME received " << frame;
   visitor_->OnWindowUpdateFrame(frame);
@@ -1455,7 +1459,7 @@
   // never process a packet while an ACK for it cannot be encrypted.
   uber_received_packet_manager_.MaybeUpdateAckTimeout(
       should_last_packet_instigate_acks_, last_decrypted_packet_level_,
-      last_header_.packet_number, time_of_last_received_packet_,
+      last_header_.packet_number, GetTimeOfLastReceivedPacket(),
       clock_->ApproximateNow(), sent_packet_manager_.GetRttStats());
 
   ClearLastFrames();
@@ -1791,9 +1795,14 @@
              << " too far from current time:"
              << clock_->ApproximateNow().ToDebuggingValue();
   }
-  time_of_last_received_packet_ = packet.receipt_time();
+  if (use_idle_network_detector_) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_idle_network_detector, 1, 6);
+    idle_network_detector_.OnPacketReceived(packet.receipt_time());
+  } else {
+    time_of_last_received_packet_ = packet.receipt_time();
+  }
   QUIC_DVLOG(1) << ENDPOINT << "time of last received packet: "
-                << time_of_last_received_packet_.ToDebuggingValue();
+                << GetTimeOfLastReceivedPacket().ToDebuggingValue();
 
   ScopedPacketFlusher flusher(this);
   if (!framer_.ProcessPacket(packet)) {
@@ -2358,12 +2367,15 @@
       SetPathDegradingAlarm();
     }
 
-    // Update |time_of_first_packet_sent_after_receiving_| if this is the
-    // first packet sent after the last packet was received. If it were
-    // updated on every sent packet, then sending into a black hole might
-    // never timeout.
-    if (time_of_first_packet_sent_after_receiving_ <
-        time_of_last_received_packet_) {
+    if (use_idle_network_detector_) {
+      idle_network_detector_.OnPacketSent(packet_send_time);
+      QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_idle_network_detector, 2, 6);
+    } else if (time_of_first_packet_sent_after_receiving_ <
+               time_of_last_received_packet_) {
+      // Update |time_of_first_packet_sent_after_receiving_| if this is the
+      // first packet sent after the last packet was received. If it were
+      // updated on every sent packet, then sending into a black hole might
+      // never timeout.
       time_of_first_packet_sent_after_receiving_ = packet_send_time;
     }
   }
@@ -3058,6 +3070,10 @@
     QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_blackhole_detector, 4, 4);
     blackhole_detector_.StopDetection();
   }
+  if (use_idle_network_detector_) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_idle_network_detector, 3, 6);
+    idle_network_detector_.StopDetection();
+  }
 }
 
 QuicByteCount QuicConnection::max_packet_length() const {
@@ -3102,6 +3118,11 @@
   } else if (idle_timeout > QuicTime::Delta::FromSeconds(1)) {
     idle_timeout = idle_timeout - QuicTime::Delta::FromSeconds(1);
   }
+  if (use_idle_network_detector_) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_idle_network_detector, 4, 6);
+    idle_network_detector_.SetTimeouts(handshake_timeout, idle_timeout);
+    return;
+  }
   handshake_timeout_ = handshake_timeout;
   idle_network_timeout_ = idle_timeout;
 
@@ -3109,6 +3130,7 @@
 }
 
 void QuicConnection::CheckForTimeout() {
+  DCHECK(!use_idle_network_detector_);
   QuicTime now = clock_->ApproximateNow();
   if (!handshake_timeout_.IsInfinite()) {
     QuicTime::Delta connected_duration = now - stats_.connection_creation_time;
@@ -3163,6 +3185,7 @@
 }
 
 void QuicConnection::SetTimeoutAlarm() {
+  DCHECK(!use_idle_network_detector_);
   QuicTime time_of_last_packet =
       std::max(time_of_last_received_packet_,
                time_of_first_packet_sent_after_receiving_);
@@ -4190,6 +4213,43 @@
                   ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
 }
 
+void QuicConnection::OnHandshakeTimeout() {
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_idle_network_detector, 5, 6);
+  DCHECK(use_idle_network_detector_);
+  const QuicTime::Delta duration =
+      clock_->ApproximateNow() - stats_.connection_creation_time;
+  const std::string error_details = quiche::QuicheStrCat(
+      "Handshake timeout expired after ", duration.ToDebuggingValue(),
+      ". Timeout:",
+      idle_network_detector_.handshake_timeout().ToDebuggingValue());
+  QUIC_DVLOG(1) << ENDPOINT << error_details;
+  CloseConnection(QUIC_HANDSHAKE_TIMEOUT, error_details,
+                  ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+}
+
+void QuicConnection::OnIdleNetworkDetected() {
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_idle_network_detector, 6, 6);
+  DCHECK(use_idle_network_detector_);
+  const QuicTime::Delta duration =
+      clock_->ApproximateNow() -
+      idle_network_detector_.last_network_activity_time();
+  const std::string error_details = quiche::QuicheStrCat(
+      "No recent network activity after ", duration.ToDebuggingValue(),
+      ". Timeout:",
+      idle_network_detector_.idle_network_timeout().ToDebuggingValue());
+  QUIC_DVLOG(1) << ENDPOINT << error_details;
+  if ((sent_packet_manager_.GetConsecutiveTlpCount() > 0 ||
+       sent_packet_manager_.GetConsecutiveRtoCount() > 0 ||
+       sent_packet_manager_.GetConsecutivePtoCount() > 0 ||
+       visitor_->ShouldKeepConnectionAlive())) {
+    CloseConnection(QUIC_NETWORK_IDLE_TIMEOUT, error_details,
+                    ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return;
+  }
+  CloseConnection(QUIC_NETWORK_IDLE_TIMEOUT, error_details,
+                  idle_timeout_connection_close_behavior_);
+}
+
 QuicTime QuicConnection::GetPathDegradingDeadline() const {
   DCHECK(use_blackhole_detector_);
   if (!ShouldDetectPathDegrading()) {
@@ -4201,8 +4261,14 @@
 
 bool QuicConnection::ShouldDetectPathDegrading() const {
   DCHECK(use_blackhole_detector_);
-  return connected_ && handshake_timeout_.IsInfinite() &&
-         perspective_ == Perspective::IS_CLIENT && !is_path_degrading_;
+  if (!connected_) {
+    return false;
+  }
+  // No path degrading detection before handshake completes.
+  if (!GetHandshakeTimeout().IsInfinite()) {
+    return false;
+  }
+  return perspective_ == Perspective::IS_CLIENT && !is_path_degrading_;
 }
 
 QuicTime QuicConnection::GetNetworkBlackholeDeadline() const {
@@ -4219,13 +4285,27 @@
   if (!connected_) {
     return false;
   }
-  if (!handshake_timeout_.IsInfinite()) {
-    // No blackhole detection before handshake completes.
+  // No blackhole detection before handshake completes.
+  if (!GetHandshakeTimeout().IsInfinite()) {
     return false;
   }
   return close_connection_after_five_rtos_ ||
          (sent_packet_manager_.pto_enabled() && max_consecutive_ptos_ > 0);
 }
 
+QuicTime::Delta QuicConnection::GetHandshakeTimeout() const {
+  if (use_idle_network_detector_) {
+    return idle_network_detector_.handshake_timeout();
+  }
+  return handshake_timeout_;
+}
+
+QuicTime QuicConnection::GetTimeOfLastReceivedPacket() const {
+  if (use_idle_network_detector_) {
+    return idle_network_detector_.time_of_last_received_packet();
+  }
+  return time_of_last_received_packet_;
+}
+
 #undef ENDPOINT  // undef for jumbo builds
 }  // namespace quic
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index cd478a6..a6be992 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -35,6 +35,7 @@
 #include "net/third_party/quiche/src/quic/core/quic_connection_id.h"
 #include "net/third_party/quiche/src/quic/core/quic_connection_stats.h"
 #include "net/third_party/quiche/src/quic/core/quic_framer.h"
+#include "net/third_party/quiche/src/quic/core/quic_idle_network_detector.h"
 #include "net/third_party/quiche/src/quic/core/quic_mtu_discovery.h"
 #include "net/third_party/quiche/src/quic/core/quic_network_blackhole_detector.h"
 #include "net/third_party/quiche/src/quic/core/quic_one_block_arena.h"
@@ -350,7 +351,8 @@
       public QuicBlockedWriterInterface,
       public QuicPacketCreator::DelegateInterface,
       public QuicSentPacketManager::NetworkChangeVisitor,
-      public QuicNetworkBlackholeDetector::Delegate {
+      public QuicNetworkBlackholeDetector::Delegate,
+      public QuicIdleNetworkDetector::Delegate {
  public:
   // Constructs a new QuicConnection for |connection_id| and
   // |initial_peer_address| using |writer| to write packets. |owns_writer|
@@ -581,6 +583,10 @@
   void OnPathDegradingDetected() override;
   void OnBlackholeDetected() override;
 
+  // QuicIdleNetworkDetector::Delegate
+  void OnHandshakeTimeout() override;
+  void OnIdleNetworkDetected() override;
+
   // Please note, this is not a const function. For logging purpose, please use
   // ack_frame().
   const QuicFrame GetUpdatedAckFrame();
@@ -1241,6 +1247,10 @@
   // Returns true if network blackhole should be detected.
   bool ShouldDetectBlackhole() const;
 
+  // Remove these two when deprecating quic_use_idle_network_detector.
+  QuicTime::Delta GetHandshakeTimeout() const;
+  QuicTime GetTimeOfLastReceivedPacket() const;
+
   QuicFramer framer_;
 
   // Contents received in the current packet, especially used to identify
@@ -1393,6 +1403,7 @@
   // An alarm that is scheduled when the connection can still write and there
   // may be more data to send.
   // An alarm that fires when the connection may have timed out.
+  // TODO(fayang): Remove this when deprecating quic_use_idle_network_detector.
   QuicArenaScopedPtr<QuicAlarm> timeout_alarm_;
   // An alarm that fires when a ping should be sent.
   QuicArenaScopedPtr<QuicAlarm> ping_alarm_;
@@ -1411,6 +1422,8 @@
 
   QuicPacketCreator packet_creator_;
 
+  // TODO(fayang): Remove these two when deprecating
+  // quic_use_idle_network_detector.
   // Network idle time before this connection is closed.
   QuicTime::Delta idle_network_timeout_;
   // The connection will wait this long for the handshake to complete.
@@ -1422,6 +1435,8 @@
   // Timestamps used for timeouts.
   // The time of the first retransmittable packet that was sent after the most
   // recently received packet.
+  // TODO(fayang): Remove these two when deprecating
+  // quic_use_idle_network_detector.
   QuicTime time_of_first_packet_sent_after_receiving_;
   // The time that a packet is received for this connection. Initialized to
   // connection creation time.
@@ -1587,8 +1602,14 @@
 
   QuicNetworkBlackholeDetector blackhole_detector_;
 
+  QuicIdleNetworkDetector idle_network_detector_;
+
   const bool use_blackhole_detector_ =
       GetQuicReloadableFlag(quic_use_blackhole_detector);
+
+  const bool use_idle_network_detector_ =
+      use_blackhole_detector_ &&
+      GetQuicReloadableFlag(quic_use_idle_network_detector);
 };
 
 }  // namespace quic
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 1b1511b..e5faa35 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -844,6 +844,11 @@
   }
 
   TestAlarmFactory::TestAlarm* GetTimeoutAlarm() {
+    if (GetQuicReloadableFlag(quic_use_blackhole_detector) &&
+        GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+      return reinterpret_cast<TestAlarmFactory::TestAlarm*>(
+          QuicConnectionPeer::GetIdleNetworkDetectorAlarm(this));
+    }
     return reinterpret_cast<TestAlarmFactory::TestAlarm*>(
         QuicConnectionPeer::GetTimeoutAlarm(this));
   }
@@ -4777,7 +4782,7 @@
   EXPECT_TRUE(connection_.connected());
 
   // Advance the time and send the first packet to the peer.
-  clock_.AdvanceTime(QuicTime::Delta::FromMicroseconds(20));
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(20));
   QuicPacketNumber last_packet;
   SendStreamDataToPeer(1, "foo", 0, NO_FIN, &last_packet);
   EXPECT_EQ(QuicPacketNumber(1u), last_packet);
@@ -4790,7 +4795,10 @@
   EXPECT_CALL(visitor_, OnConnectionClosed(_, _)).Times(0);
   QuicTime::Delta delay = initial_ddl - clock_.ApproximateNow();
   clock_.AdvanceTime(delay);
-  connection_.GetTimeoutAlarm()->Fire();
+  if (!GetQuicReloadableFlag(quic_use_blackhole_detector) ||
+      !GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+    connection_.GetTimeoutAlarm()->Fire();
+  }
   // Verify the timeout alarm deadline is updated.
   EXPECT_TRUE(connection_.connected());
   EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
@@ -4879,8 +4887,11 @@
   EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _));
   ProcessAckPacket(&frame);
 
-  // Fire early to verify it wouldn't timeout yet.
-  connection_.GetTimeoutAlarm()->Fire();
+  if (!GetQuicReloadableFlag(quic_use_blackhole_detector) ||
+      !GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+    // Fire early to verify it wouldn't timeout yet.
+    connection_.GetTimeoutAlarm()->Fire();
+  }
   EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
   EXPECT_TRUE(connection_.connected());
 
@@ -5627,7 +5638,13 @@
   SendStreamDataToPeer(
       GetNthClientInitiatedStreamId(1, connection_.transport_version()), "foo",
       0, FIN, nullptr);
-  EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+  if (GetQuicReloadableFlag(quic_use_blackhole_detector) &&
+      GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+    EXPECT_EQ(default_timeout + five_ms,
+              connection_.GetTimeoutAlarm()->deadline());
+  } else {
+    EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+  }
 
   // Now send more data. This will not move the timeout because
   // no data has been received since the previous write.
@@ -5635,13 +5652,22 @@
   SendStreamDataToPeer(
       GetNthClientInitiatedStreamId(1, connection_.transport_version()), "foo",
       3, FIN, nullptr);
-  EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+  if (GetQuicReloadableFlag(quic_use_blackhole_detector) &&
+      GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+    EXPECT_EQ(default_timeout + five_ms,
+              connection_.GetTimeoutAlarm()->deadline());
+  } else {
+    EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+  }
 
   // The original alarm will fire.  We should not time out because we had a
   // network event at t=5ms.  The alarm will reregister.
   clock_.AdvanceTime(initial_idle_timeout - five_ms - five_ms);
   EXPECT_EQ(default_timeout, clock_.ApproximateNow());
-  connection_.GetTimeoutAlarm()->Fire();
+  if (!GetQuicReloadableFlag(quic_use_blackhole_detector) ||
+      !GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+    connection_.GetTimeoutAlarm()->Fire();
+  }
   EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
   EXPECT_TRUE(connection_.connected());
   EXPECT_EQ(default_timeout + five_ms,
@@ -5690,7 +5716,13 @@
   SendStreamDataToPeer(
       GetNthClientInitiatedStreamId(1, connection_.transport_version()), "foo",
       0, FIN, nullptr);
-  EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+  if (GetQuicReloadableFlag(quic_use_blackhole_detector) &&
+      GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+    EXPECT_EQ(default_timeout + five_ms,
+              connection_.GetTimeoutAlarm()->deadline());
+  } else {
+    EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+  }
 
   // Move forward 5 ms and receive a packet, which will move the timeout
   // forward 5 ms more (but will not reschedule the alarm).
@@ -5719,7 +5751,10 @@
   ASSERT_EQ(default_timeout.ToDebuggingValue(),
             clock_.Now().ToDebuggingValue());
   EXPECT_EQ(default_timeout, clock_.Now());
-  connection_.GetTimeoutAlarm()->Fire();
+  if (!GetQuicReloadableFlag(quic_use_blackhole_detector) ||
+      !GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+    connection_.GetTimeoutAlarm()->Fire();
+  }
   EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
   EXPECT_TRUE(connection_.connected());
   ASSERT_EQ(final_timeout.ToDebuggingValue(),
@@ -5775,7 +5810,13 @@
   SendStreamDataToPeer(
       GetNthClientInitiatedStreamId(1, connection_.transport_version()), "foo",
       0, FIN, nullptr);
-  EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+  if (GetQuicReloadableFlag(quic_use_blackhole_detector) &&
+      GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+    EXPECT_EQ(default_timeout + five_ms,
+              connection_.GetTimeoutAlarm()->deadline());
+  } else {
+    EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+  }
 
   // Now send more data. This will not move the timeout because
   // no data has been received since the previous write.
@@ -5783,13 +5824,22 @@
   SendStreamDataToPeer(
       GetNthClientInitiatedStreamId(1, connection_.transport_version()), "foo",
       3, FIN, nullptr);
-  EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+  if (GetQuicReloadableFlag(quic_use_blackhole_detector) &&
+      GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+    EXPECT_EQ(default_timeout + five_ms,
+              connection_.GetTimeoutAlarm()->deadline());
+  } else {
+    EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+  }
 
   // The original alarm will fire.  We should not time out because we had a
   // network event at t=5ms.  The alarm will reregister.
   clock_.AdvanceTime(default_idle_timeout - five_ms - five_ms);
   EXPECT_EQ(default_timeout, clock_.ApproximateNow());
-  connection_.GetTimeoutAlarm()->Fire();
+  if (!GetQuicReloadableFlag(quic_use_blackhole_detector) ||
+      !GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+    connection_.GetTimeoutAlarm()->Fire();
+  }
   EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
   EXPECT_TRUE(connection_.connected());
   EXPECT_EQ(default_timeout + five_ms,
@@ -5852,7 +5902,13 @@
   SendStreamDataToPeer(
       GetNthClientInitiatedStreamId(1, connection_.transport_version()), "foo",
       0, FIN, nullptr);
-  EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+  if (GetQuicReloadableFlag(quic_use_blackhole_detector) &&
+      GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+    EXPECT_EQ(default_timeout + five_ms,
+              connection_.GetTimeoutAlarm()->deadline());
+  } else {
+    EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+  }
 
   // Retransmit the packet via tail loss probe.
   clock_.AdvanceTime(connection_.GetRetransmissionAlarm()->deadline() -
@@ -5909,7 +5965,13 @@
   SendStreamDataToPeer(
       GetNthClientInitiatedStreamId(1, connection_.transport_version()), "foo",
       0, FIN, nullptr);
-  EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+  if (GetQuicReloadableFlag(quic_use_blackhole_detector) &&
+      GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+    EXPECT_EQ(default_timeout + five_ms,
+              connection_.GetTimeoutAlarm()->deadline());
+  } else {
+    EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+  }
 
   // Indicate streams are still open.
   EXPECT_CALL(visitor_, ShouldKeepConnectionAlive())
@@ -5960,7 +6022,10 @@
   // network event at t=5ms.  The alarm will reregister.
   clock_.AdvanceTime(initial_idle_timeout - five_ms);
   EXPECT_EQ(default_timeout, clock_.ApproximateNow());
-  connection_.GetTimeoutAlarm()->Fire();
+  if (!GetQuicReloadableFlag(quic_use_blackhole_detector) ||
+      !GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+    connection_.GetTimeoutAlarm()->Fire();
+  }
   EXPECT_TRUE(connection_.connected());
   EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
   EXPECT_EQ(default_timeout + five_ms,
@@ -6017,7 +6082,10 @@
   // network event at t=5ms.  The alarm will reregister.
   clock_.AdvanceTime(initial_idle_timeout - five_ms);
   EXPECT_EQ(default_timeout, clock_.ApproximateNow());
-  connection_.GetTimeoutAlarm()->Fire();
+  if (!GetQuicReloadableFlag(quic_use_blackhole_detector) ||
+      !GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+    connection_.GetTimeoutAlarm()->Fire();
+  }
   EXPECT_TRUE(connection_.connected());
   EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
   EXPECT_EQ(default_timeout + five_ms,
diff --git a/quic/core/quic_idle_network_detector.cc b/quic/core/quic_idle_network_detector.cc
new file mode 100644
index 0000000..8d76fd2
--- /dev/null
+++ b/quic/core/quic_idle_network_detector.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/quic_idle_network_detector.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_constants.h"
+
+namespace quic {
+
+namespace {
+
+class AlarmDelegate : public QuicAlarm::Delegate {
+ public:
+  explicit AlarmDelegate(QuicIdleNetworkDetector* detector)
+      : detector_(detector) {}
+  AlarmDelegate(const AlarmDelegate&) = delete;
+  AlarmDelegate& operator=(const AlarmDelegate&) = delete;
+
+  void OnAlarm() override { detector_->OnAlarm(); }
+
+ private:
+  QuicIdleNetworkDetector* detector_;
+};
+
+}  // namespace
+
+QuicIdleNetworkDetector::QuicIdleNetworkDetector(
+    Delegate* delegate,
+    QuicTime now,
+    QuicConnectionArena* arena,
+    QuicAlarmFactory* alarm_factory)
+    : delegate_(delegate),
+      start_time_(now),
+      handshake_timeout_(QuicTime::Delta::Infinite()),
+      time_of_last_received_packet_(now),
+      time_of_first_packet_sent_after_receiving_(QuicTime::Zero()),
+      idle_network_timeout_(QuicTime::Delta::Infinite()),
+      alarm_(
+          alarm_factory->CreateAlarm(arena->New<AlarmDelegate>(this), arena)) {}
+
+void QuicIdleNetworkDetector::OnAlarm() {
+  if (handshake_timeout_.IsInfinite()) {
+    delegate_->OnIdleNetworkDetected();
+    return;
+  }
+  if (idle_network_timeout_.IsInfinite()) {
+    delegate_->OnHandshakeTimeout();
+    return;
+  }
+  if (last_network_activity_time() + idle_network_timeout_ >
+      start_time_ + handshake_timeout_) {
+    delegate_->OnHandshakeTimeout();
+    return;
+  }
+  delegate_->OnIdleNetworkDetected();
+}
+
+void QuicIdleNetworkDetector::SetTimeouts(
+    QuicTime::Delta handshake_timeout,
+    QuicTime::Delta idle_network_timeout) {
+  handshake_timeout_ = handshake_timeout;
+  idle_network_timeout_ = idle_network_timeout;
+
+  SetAlarm();
+}
+
+void QuicIdleNetworkDetector::StopDetection() {
+  alarm_->Cancel();
+  handshake_timeout_ = QuicTime::Delta::Infinite();
+  idle_network_timeout_ = QuicTime::Delta::Infinite();
+}
+
+void QuicIdleNetworkDetector::OnPacketSent(QuicTime now) {
+  if (time_of_first_packet_sent_after_receiving_ >
+      time_of_last_received_packet_) {
+    return;
+  }
+  time_of_first_packet_sent_after_receiving_ =
+      std::max(time_of_first_packet_sent_after_receiving_, now);
+
+  SetAlarm();
+}
+
+void QuicIdleNetworkDetector::OnPacketReceived(QuicTime now) {
+  time_of_last_received_packet_ = std::max(time_of_last_received_packet_, now);
+
+  SetAlarm();
+}
+
+void QuicIdleNetworkDetector::SetAlarm() {
+  // Set alarm to the nearer deadline.
+  QuicTime new_deadline = QuicTime::Zero();
+  if (!handshake_timeout_.IsInfinite()) {
+    new_deadline = start_time_ + handshake_timeout_;
+  }
+  if (!idle_network_timeout_.IsInfinite()) {
+    const QuicTime idle_network_deadline =
+        last_network_activity_time() + idle_network_timeout_;
+    if (new_deadline.IsInitialized()) {
+      new_deadline = std::min(new_deadline, idle_network_deadline);
+    } else {
+      new_deadline = idle_network_deadline;
+    }
+  }
+  alarm_->Update(new_deadline, kAlarmGranularity);
+}
+
+}  // namespace quic
diff --git a/quic/core/quic_idle_network_detector.h b/quic/core/quic_idle_network_detector.h
new file mode 100644
index 0000000..83beb29
--- /dev/null
+++ b/quic/core/quic_idle_network_detector.h
@@ -0,0 +1,103 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QUIC_IDLE_NETWORK_DETECTOR_H_
+#define QUICHE_QUIC_CORE_QUIC_IDLE_NETWORK_DETECTOR_H_
+
+#include "net/third_party/quiche/src/quic/core/quic_alarm.h"
+#include "net/third_party/quiche/src/quic/core/quic_alarm_factory.h"
+#include "net/third_party/quiche/src/quic/core/quic_one_block_arena.h"
+#include "net/third_party/quiche/src/quic/core/quic_time.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+
+namespace quic {
+
+namespace test {
+class QuicConnectionPeer;
+class QuicIdleNetworkDetectorTestPeer;
+}  // namespace test
+
+// QuicIdleNetworkDetector detects handshake timeout and idle network timeout.
+// Handshake timeout detection is disabled after handshake completes. Idle
+// network deadline is extended by network activity (e.g., sending or receiving
+// packets).
+class QUIC_EXPORT_PRIVATE QuicIdleNetworkDetector {
+ public:
+  class QUIC_EXPORT_PRIVATE Delegate {
+   public:
+    virtual ~Delegate() {}
+
+    // Called when handshake times out.
+    virtual void OnHandshakeTimeout() = 0;
+
+    // Called when idle network has been detected.
+    virtual void OnIdleNetworkDetected() = 0;
+  };
+
+  QuicIdleNetworkDetector(Delegate* delegate,
+                          QuicTime now,
+                          QuicConnectionArena* arena,
+                          QuicAlarmFactory* alarm_factory);
+
+  void OnAlarm();
+
+  // Called to set handshake_timeout_ and idle_network_timeout_.
+  void SetTimeouts(QuicTime::Delta handshake_timeout,
+                   QuicTime::Delta idle_network_timeout);
+
+  void StopDetection();
+
+  // Called when a packet gets sent.
+  void OnPacketSent(QuicTime now);
+
+  // Called when a packet gets received.
+  void OnPacketReceived(QuicTime now);
+
+  QuicTime::Delta handshake_timeout() const { return handshake_timeout_; }
+
+  QuicTime time_of_last_received_packet() const {
+    return time_of_last_received_packet_;
+  }
+
+  QuicTime last_network_activity_time() const {
+    return std::max(time_of_last_received_packet_,
+                    time_of_first_packet_sent_after_receiving_);
+  }
+
+  QuicTime::Delta idle_network_timeout() const { return idle_network_timeout_; }
+
+ private:
+  friend class test::QuicConnectionPeer;
+  friend class test::QuicIdleNetworkDetectorTestPeer;
+
+  void SetAlarm();
+
+  Delegate* delegate_;  // Not owned.
+
+  // Start time of the detector, handshake deadline = start_time_ +
+  // handshake_timeout_.
+  const QuicTime start_time_;
+
+  // Handshake timeout. Infinit means handshake has completed.
+  QuicTime::Delta handshake_timeout_;
+
+  // Time that last packet is received for this connection. Initialized to
+  // start_time_.
+  QuicTime time_of_last_received_packet_;
+
+  // Time that the first packet gets sent after the received packet. idle
+  // network deadline = std::max(time_of_last_received_packet_,
+  // time_of_first_packet_sent_after_receiving_) + idle_network_timeout_.
+  // Initialized to 0.
+  QuicTime time_of_first_packet_sent_after_receiving_;
+
+  // Idle network timeout. Infinit means no idle network timeout.
+  QuicTime::Delta idle_network_timeout_;
+
+  QuicArenaScopedPtr<QuicAlarm> alarm_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QUIC_IDLE_NETWORK_DETECTOR_H_
diff --git a/quic/core/quic_idle_network_detector_test.cc b/quic/core/quic_idle_network_detector_test.cc
new file mode 100644
index 0000000..16941ef
--- /dev/null
+++ b/quic/core/quic_idle_network_detector_test.cc
@@ -0,0 +1,148 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/quic_idle_network_detector.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_one_block_arena.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+
+namespace quic {
+namespace test {
+
+class QuicIdleNetworkDetectorTestPeer {
+ public:
+  static QuicAlarm* GetAlarm(QuicIdleNetworkDetector* detector) {
+    return detector->alarm_.get();
+  }
+};
+
+namespace {
+
+class MockDelegate : public QuicIdleNetworkDetector::Delegate {
+ public:
+  MOCK_METHOD0(OnHandshakeTimeout, void());
+  MOCK_METHOD0(OnIdleNetworkDetected, void());
+};
+
+class QuicIdleNetworkDetectorTest : public QuicTest {
+ public:
+  QuicIdleNetworkDetectorTest() {
+    clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1));
+    detector_ = std::make_unique<QuicIdleNetworkDetector>(
+        &delegate_, clock_.Now(), &arena_, &alarm_factory_);
+    alarm_ = static_cast<MockAlarmFactory::TestAlarm*>(
+        QuicIdleNetworkDetectorTestPeer::GetAlarm(detector_.get()));
+  }
+
+ protected:
+  testing::StrictMock<MockDelegate> delegate_;
+  QuicConnectionArena arena_;
+  MockAlarmFactory alarm_factory_;
+
+  std::unique_ptr<QuicIdleNetworkDetector> detector_;
+
+  MockAlarmFactory::TestAlarm* alarm_;
+  MockClock clock_;
+};
+
+TEST_F(QuicIdleNetworkDetectorTest,
+       IdleNetworkDetectedBeforeHandshakeCompletes) {
+  EXPECT_FALSE(alarm_->IsSet());
+  detector_->SetTimeouts(
+      /*handshake_timeout=*/QuicTime::Delta::FromSeconds(30),
+      /*idle_network_timeout=*/QuicTime::Delta::FromSeconds(20));
+  EXPECT_TRUE(alarm_->IsSet());
+  EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(20),
+            alarm_->deadline());
+
+  // No network activity for 20s.
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(20));
+  EXPECT_CALL(delegate_, OnIdleNetworkDetected());
+  alarm_->Fire();
+}
+
+TEST_F(QuicIdleNetworkDetectorTest, HandshakeTimeout) {
+  EXPECT_FALSE(alarm_->IsSet());
+  detector_->SetTimeouts(
+      /*handshake_timeout=*/QuicTime::Delta::FromSeconds(30),
+      /*idle_network_timeout=*/QuicTime::Delta::FromSeconds(20));
+  EXPECT_TRUE(alarm_->IsSet());
+
+  // Has network activity after 15s.
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(15));
+  detector_->OnPacketReceived(clock_.Now());
+  EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(15),
+            alarm_->deadline());
+  // Handshake does not complete for another 15s.
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(15));
+  EXPECT_CALL(delegate_, OnHandshakeTimeout());
+  alarm_->Fire();
+}
+
+TEST_F(QuicIdleNetworkDetectorTest,
+       IdleNetworkDetectedAfterHandshakeCompletes) {
+  EXPECT_FALSE(alarm_->IsSet());
+  detector_->SetTimeouts(
+      /*handshake_timeout=*/QuicTime::Delta::FromSeconds(30),
+      /*idle_network_timeout=*/QuicTime::Delta::FromSeconds(20));
+  EXPECT_TRUE(alarm_->IsSet());
+
+  // Handshake completes in 200ms.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(200));
+  detector_->OnPacketReceived(clock_.Now());
+  detector_->SetTimeouts(
+      /*handshake_timeout=*/QuicTime::Delta::Infinite(),
+      /*idle_network_timeout=*/QuicTime::Delta::FromSeconds(600));
+  EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(600),
+            alarm_->deadline());
+
+  // No network activity for 600s.
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(600));
+  EXPECT_CALL(delegate_, OnIdleNetworkDetected());
+  alarm_->Fire();
+}
+
+TEST_F(QuicIdleNetworkDetectorTest,
+       DoNotExtendIdleDeadlineOnConsecutiveSentPackets) {
+  EXPECT_FALSE(alarm_->IsSet());
+  detector_->SetTimeouts(
+      /*handshake_timeout=*/QuicTime::Delta::FromSeconds(30),
+      /*idle_network_timeout=*/QuicTime::Delta::FromSeconds(20));
+  EXPECT_TRUE(alarm_->IsSet());
+
+  // Handshake completes in 200ms.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(200));
+  detector_->OnPacketReceived(clock_.Now());
+  detector_->SetTimeouts(
+      /*handshake_timeout=*/QuicTime::Delta::Infinite(),
+      /*idle_network_timeout=*/QuicTime::Delta::FromSeconds(600));
+  EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(600),
+            alarm_->deadline());
+
+  // Sent packets after 200ms.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(200));
+  detector_->OnPacketSent(clock_.Now());
+  const QuicTime packet_sent_time = clock_.Now();
+  EXPECT_EQ(packet_sent_time + QuicTime::Delta::FromSeconds(600),
+            alarm_->deadline());
+
+  // Sent another packet after 200ms
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(200));
+  detector_->OnPacketSent(clock_.Now());
+  // Verify idle network deadline does not extend.
+  EXPECT_EQ(packet_sent_time + QuicTime::Delta::FromSeconds(600),
+            alarm_->deadline());
+
+  // No network activity for 600s.
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(600) -
+                     QuicTime::Delta::FromMilliseconds(200));
+  EXPECT_CALL(delegate_, OnIdleNetworkDetected());
+  alarm_->Fire();
+}
+
+}  // namespace
+
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/quic_one_block_arena.h b/quic/core/quic_one_block_arena.h
index 41842f3..210892a 100644
--- a/quic/core/quic_one_block_arena.h
+++ b/quic/core/quic_one_block_arena.h
@@ -75,7 +75,9 @@
 
 // QuicConnections currently use around 1KB of polymorphic types which would
 // ordinarily be on the heap. Instead, store them inline in an arena.
-using QuicConnectionArena = QuicOneBlockArena<1024>;
+// TODO(fayang): Switch this back to 1024 when deprecating
+// quic_use_blackhole_detector or quic_use_idle_network_detector.
+using QuicConnectionArena = QuicOneBlockArena<1200>;
 
 }  // namespace quic
 
diff --git a/quic/test_tools/quic_connection_peer.cc b/quic/test_tools/quic_connection_peer.cc
index 21a0105..6b6387f 100644
--- a/quic/test_tools/quic_connection_peer.cc
+++ b/quic/test_tools/quic_connection_peer.cc
@@ -51,6 +51,9 @@
 // static
 QuicTime::Delta QuicConnectionPeer::GetNetworkTimeout(
     QuicConnection* connection) {
+  if (connection->use_idle_network_detector_) {
+    return connection->idle_network_detector_.idle_network_timeout_;
+  }
   return connection->idle_network_timeout_;
 }
 
@@ -372,5 +375,11 @@
   return connection->blackhole_detector_.blackhole_deadline_;
 }
 
+// static
+QuicAlarm* QuicConnectionPeer::GetIdleNetworkDetectorAlarm(
+    QuicConnection* connection) {
+  return connection->idle_network_detector_.alarm_.get();
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quic/test_tools/quic_connection_peer.h b/quic/test_tools/quic_connection_peer.h
index ab14190..d6d6b3c 100644
--- a/quic/test_tools/quic_connection_peer.h
+++ b/quic/test_tools/quic_connection_peer.h
@@ -144,6 +144,8 @@
   static QuicTime GetPathDegradingDeadline(QuicConnection* connection);
 
   static QuicTime GetBlackholeDetectionDeadline(QuicConnection* connection);
+
+  static QuicAlarm* GetIdleNetworkDetectorAlarm(QuicConnection* connection);
 };
 
 }  // namespace test