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);