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