QUIC client do not send new request if now is close to idle timeout. Only affecting client, not protected.
PiperOrigin-RevId: 323845829
Change-Id: Iabaddc08fb901d28e228cd89d96add2ef4e88e85
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index 2fc3098..ff909e9 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -639,6 +639,28 @@
legacy_version_encapsulation_sni_ = server_name;
}
+bool QuicConnection::MaybeTestLiveness() {
+ if (encryption_level_ != ENCRYPTION_FORWARD_SECURE) {
+ return false;
+ }
+ const QuicTime idle_network_deadline =
+ idle_network_detector_.GetIdleNetworkDeadline();
+ if (!idle_network_deadline.IsInitialized()) {
+ return false;
+ }
+ const QuicTime now = clock_->ApproximateNow();
+ if (now > idle_network_deadline) {
+ QUIC_BUG << "Idle network deadline has passed";
+ return false;
+ }
+ const QuicTime::Delta timeout = idle_network_deadline - now;
+ if (!sent_packet_manager_.IsLessThanThreePTOs(timeout)) {
+ return false;
+ }
+ SendConnectivityProbingPacket(writer_, peer_address_);
+ return true;
+}
+
void QuicConnection::ApplyConnectionOptions(
const QuicTagVector& connection_options) {
sent_packet_manager_.ApplyConnectionOptions(connection_options);
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index 32eabef..ff7ecb4 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -990,6 +990,11 @@
// Can only be set if this is a client connection.
void EnableLegacyVersionEncapsulation(const std::string& server_name);
+ // If now is close to idle timeout, returns true and sends a connectivity
+ // probing packet to test the connection for liveness. Otherwise, returns
+ // false.
+ bool MaybeTestLiveness();
+
protected:
// Calls cancel() on all the alarms owned by this connection.
void CancelAllAlarms();
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 5f1ff0c..f345c36 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -11547,6 +11547,50 @@
connection_.GetRetransmissionAlarm()->Fire();
}
+TEST_P(QuicConnectionTest, TestingLiveness) {
+ const size_t kMinRttMs = 40;
+ RttStats* rtt_stats = const_cast<RttStats*>(manager_->GetRttStats());
+ rtt_stats->UpdateRtt(QuicTime::Delta::FromMilliseconds(kMinRttMs),
+ QuicTime::Delta::Zero(), QuicTime::Zero());
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+ QuicConfig config;
+
+ CryptoHandshakeMessage msg;
+ std::string error_details;
+ QuicConfig client_config;
+ client_config.SetInitialStreamFlowControlWindowToSend(
+ kInitialStreamFlowControlWindowForTest);
+ client_config.SetInitialSessionFlowControlWindowToSend(
+ kInitialSessionFlowControlWindowForTest);
+ client_config.SetIdleNetworkTimeout(QuicTime::Delta::FromSeconds(30));
+ client_config.ToHandshakeMessage(&msg, connection_.transport_version());
+ const QuicErrorCode error =
+ config.ProcessPeerHello(msg, CLIENT, &error_details);
+ EXPECT_THAT(error, IsQuicNoError());
+
+ if (connection_.version().AuthenticatesHandshakeConnectionIds()) {
+ QuicConfigPeer::SetReceivedOriginalConnectionId(
+ &config, connection_.connection_id());
+ QuicConfigPeer::SetReceivedInitialSourceConnectionId(
+ &config, connection_.connection_id());
+ }
+
+ connection_.SetFromConfig(config);
+ connection_.OnHandshakeComplete();
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+ ASSERT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
+ EXPECT_FALSE(connection_.MaybeTestLiveness());
+
+ QuicTime deadline = connection_.GetTimeoutAlarm()->deadline();
+ QuicTime::Delta timeout = deadline - clock_.ApproximateNow();
+ // Advance time to near the idle timeout.
+ clock_.AdvanceTime(timeout - QuicTime::Delta::FromMilliseconds(1));
+ EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
+ EXPECT_TRUE(connection_.MaybeTestLiveness());
+ // Verify idle deadline does not change.
+ EXPECT_EQ(deadline, connection_.GetTimeoutAlarm()->deadline());
+}
+
} // namespace
} // namespace test
} // namespace quic
diff --git a/quic/core/quic_idle_network_detector.cc b/quic/core/quic_idle_network_detector.cc
index 8d76fd2..a033f7a 100644
--- a/quic/core/quic_idle_network_detector.cc
+++ b/quic/core/quic_idle_network_detector.cc
@@ -95,8 +95,7 @@
new_deadline = start_time_ + handshake_timeout_;
}
if (!idle_network_timeout_.IsInfinite()) {
- const QuicTime idle_network_deadline =
- last_network_activity_time() + idle_network_timeout_;
+ const QuicTime idle_network_deadline = GetIdleNetworkDeadline();
if (new_deadline.IsInitialized()) {
new_deadline = std::min(new_deadline, idle_network_deadline);
} else {
@@ -106,4 +105,11 @@
alarm_->Update(new_deadline, kAlarmGranularity);
}
+QuicTime QuicIdleNetworkDetector::GetIdleNetworkDeadline() const {
+ if (idle_network_timeout_.IsInfinite()) {
+ return QuicTime::Zero();
+ }
+ return last_network_activity_time() + idle_network_timeout_;
+}
+
} // namespace quic
diff --git a/quic/core/quic_idle_network_detector.h b/quic/core/quic_idle_network_detector.h
index 83beb29..1537222 100644
--- a/quic/core/quic_idle_network_detector.h
+++ b/quic/core/quic_idle_network_detector.h
@@ -67,6 +67,8 @@
QuicTime::Delta idle_network_timeout() const { return idle_network_timeout_; }
+ QuicTime GetIdleNetworkDeadline() const;
+
private:
friend class test::QuicConnectionPeer;
friend class test::QuicIdleNetworkDetectorTestPeer;
diff --git a/quic/core/quic_sent_packet_manager.cc b/quic/core/quic_sent_packet_manager.cc
index ae536a2..1a97417 100644
--- a/quic/core/quic_sent_packet_manager.cc
+++ b/quic/core/quic_sent_packet_manager.cc
@@ -1576,5 +1576,12 @@
return handshake_finished_ || handshake_packet_acked_;
}
+bool QuicSentPacketManager::IsLessThanThreePTOs(QuicTime::Delta timeout) const {
+ const QuicTime::Delta retransmission_delay =
+ pto_enabled_ ? GetProbeTimeoutDelay(APPLICATION_DATA)
+ : GetRetransmissionDelay();
+ return timeout < 3 * retransmission_delay;
+}
+
#undef ENDPOINT // undef for jumbo builds
} // namespace quic
diff --git a/quic/core/quic_sent_packet_manager.h b/quic/core/quic_sent_packet_manager.h
index 4e6cab1..e2af3e2 100644
--- a/quic/core/quic_sent_packet_manager.h
+++ b/quic/core/quic_sent_packet_manager.h
@@ -408,6 +408,9 @@
// Called to retransmit in flight packet of |space| if any.
void RetransmitDataOfSpaceIfAny(PacketNumberSpace space);
+ // Returns true if |timeout| is less than 3 * RTO/PTO delay.
+ bool IsLessThanThreePTOs(QuicTime::Delta timeout) const;
+
bool supports_multiple_packet_number_spaces() const {
return unacked_packets_.supports_multiple_packet_number_spaces();
}
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 99bbb0e..694d76e 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -99,6 +99,7 @@
is_configured_(false),
enable_round_robin_scheduling_(false),
was_zero_rtt_rejected_(false),
+ liveness_testing_in_progress_(false),
fix_gquic_stream_type_(GetQuicReloadableFlag(quic_fix_gquic_stream_type)),
remove_streams_waiting_for_acks_(
GetQuicReloadableFlag(quic_remove_streams_waiting_for_acks)),
@@ -264,6 +265,10 @@
void QuicSession::OnPacketDecrypted(EncryptionLevel level) {
GetMutableCryptoStream()->OnPacketDecrypted(level);
+ if (liveness_testing_in_progress_) {
+ liveness_testing_in_progress_ = false;
+ OnCanCreateNewOutgoingStream(/*unidirectional=*/false);
+ }
}
void QuicSession::OnOneRttPacketAcknowledged() {
@@ -1746,19 +1751,33 @@
}
bool QuicSession::CanOpenNextOutgoingBidirectionalStream() {
+ if (liveness_testing_in_progress_) {
+ DCHECK_EQ(Perspective::IS_CLIENT, perspective());
+ return false;
+ }
if (!VersionHasIetfQuicFrames(transport_version())) {
- return stream_id_manager_.CanOpenNextOutgoingStream();
+ if (!stream_id_manager_.CanOpenNextOutgoingStream()) {
+ return false;
+ }
+ } else {
+ if (!v99_streamid_manager_.CanOpenNextOutgoingBidirectionalStream()) {
+ if (is_configured_) {
+ // Send STREAM_BLOCKED after config negotiated.
+ control_frame_manager_.WriteOrBufferStreamsBlocked(
+ v99_streamid_manager_.max_outgoing_bidirectional_streams(),
+ /*unidirectional=*/false);
+ }
+ return false;
+ }
}
- if (v99_streamid_manager_.CanOpenNextOutgoingBidirectionalStream()) {
- return true;
+ if (perspective() == Perspective::IS_CLIENT &&
+ connection_->MaybeTestLiveness()) {
+ // Now is relatively close to the idle timeout having the risk that requests
+ // could be discarded at the server.
+ liveness_testing_in_progress_ = true;
+ return false;
}
- if (is_configured_) {
- // Send STREAM_BLOCKED after config negotiated.
- control_frame_manager_.WriteOrBufferStreamsBlocked(
- v99_streamid_manager_.max_outgoing_bidirectional_streams(),
- /*unidirectional=*/false);
- }
- return false;
+ return true;
}
bool QuicSession::CanOpenNextOutgoingUnidirectionalStream() {
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h
index 09ce2fd..b15bb3a 100644
--- a/quic/core/quic_session.h
+++ b/quic/core/quic_session.h
@@ -845,6 +845,10 @@
// Whether the session has received a 0-RTT rejection (QUIC+TLS only).
bool was_zero_rtt_rejected_;
+ // This indicates a liveness testing is in progress, and push back the
+ // creation of new outgoing bidirectional streams.
+ bool liveness_testing_in_progress_;
+
// Latched value of flag quic_fix_gquic_stream_type.
const bool fix_gquic_stream_type_;
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index 2d8cb6e..a033573 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -2215,6 +2215,28 @@
kDefaultMinAckDelayTimeMs);
}
+TEST_P(QuicSessionTestClient, FailedToCreateStreamIfTooCloseToIdleTimeout) {
+ connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+ EXPECT_TRUE(session_.CanOpenNextOutgoingBidirectionalStream());
+ QuicTime deadline = QuicConnectionPeer::GetIdleNetworkDeadline(connection_);
+ ASSERT_TRUE(deadline.IsInitialized());
+ QuicTime::Delta timeout = deadline - helper_.GetClock()->ApproximateNow();
+ // Advance time to very close idle timeout.
+ connection_->AdvanceTime(timeout - QuicTime::Delta::FromMilliseconds(1));
+ // Verify creation of new stream gets pushed back and connectivity probing
+ // packet gets sent.
+ EXPECT_CALL(*connection_, SendConnectivityProbingPacket(_, _)).Times(1);
+ EXPECT_FALSE(session_.CanOpenNextOutgoingBidirectionalStream());
+
+ // New packet gets received, idle deadline gets extended.
+ EXPECT_CALL(session_, OnCanCreateNewOutgoingStream(false));
+ QuicConnectionPeer::GetIdleNetworkDetector(connection_)
+ .OnPacketReceived(helper_.GetClock()->ApproximateNow());
+ session_.OnPacketDecrypted(ENCRYPTION_FORWARD_SECURE);
+
+ EXPECT_TRUE(session_.CanOpenNextOutgoingBidirectionalStream());
+}
+
TEST_P(QuicSessionTestServer, ZombieStreams) {
TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
QuicStreamPeer::SetStreamBytesWritten(3, stream2);
diff --git a/quic/test_tools/quic_connection_peer.cc b/quic/test_tools/quic_connection_peer.cc
index 5fb31a5..6f9c8c6 100644
--- a/quic/test_tools/quic_connection_peer.cc
+++ b/quic/test_tools/quic_connection_peer.cc
@@ -362,12 +362,24 @@
}
// static
+QuicTime QuicConnectionPeer::GetIdleNetworkDeadline(
+ QuicConnection* connection) {
+ return connection->idle_network_detector_.GetIdleNetworkDeadline();
+}
+
+// static
QuicAlarm* QuicConnectionPeer::GetIdleNetworkDetectorAlarm(
QuicConnection* connection) {
return connection->idle_network_detector_.alarm_.get();
}
// static
+QuicIdleNetworkDetector& QuicConnectionPeer::GetIdleNetworkDetector(
+ QuicConnection* connection) {
+ return connection->idle_network_detector_;
+}
+
+// static
void QuicConnectionPeer::SetServerConnectionId(
QuicConnection* connection,
const QuicConnectionId& server_connection_id) {
diff --git a/quic/test_tools/quic_connection_peer.h b/quic/test_tools/quic_connection_peer.h
index 8f3a78c..01d16f2 100644
--- a/quic/test_tools/quic_connection_peer.h
+++ b/quic/test_tools/quic_connection_peer.h
@@ -146,6 +146,11 @@
static QuicAlarm* GetIdleNetworkDetectorAlarm(QuicConnection* connection);
+ static QuicTime GetIdleNetworkDeadline(QuicConnection* connection);
+
+ static QuicIdleNetworkDetector& GetIdleNetworkDetector(
+ QuicConnection* connection);
+
static void SetServerConnectionId(
QuicConnection* connection,
const QuicConnectionId& server_connection_id);