| // Copyright (c) 2022 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 "quiche/quic/core/quic_ping_manager.h" | |
| #include "quiche/quic/platform/api/quic_flags.h" | |
| namespace quic { | |
| namespace { | |
| // Maximum shift used to calculate retransmittable on wire timeout. For 200ms | |
| // initial retransmittable on wire delay, this would get a maximum of 200ms * (1 | |
| // << 10) = 204.8s | |
| const int kMaxRetransmittableOnWireDelayShift = 10; | |
| class AlarmDelegate : public QuicAlarm::DelegateWithContext { | |
| public: | |
| explicit AlarmDelegate(QuicPingManager* manager, | |
| QuicConnectionContext* context) | |
| : QuicAlarm::DelegateWithContext(context), manager_(manager) {} | |
| AlarmDelegate(const AlarmDelegate&) = delete; | |
| AlarmDelegate& operator=(const AlarmDelegate&) = delete; | |
| void OnAlarm() override { manager_->OnAlarm(); } | |
| private: | |
| QuicPingManager* manager_; | |
| }; | |
| } // namespace | |
| QuicPingManager::QuicPingManager(Perspective perspective, Delegate* delegate, | |
| QuicConnectionArena* arena, | |
| QuicAlarmFactory* alarm_factory, | |
| QuicConnectionContext* context) | |
| : perspective_(perspective), | |
| delegate_(delegate), | |
| alarm_(alarm_factory->CreateAlarm( | |
| arena->New<AlarmDelegate>(this, context), arena)) {} | |
| void QuicPingManager::SetAlarm(QuicTime now, bool should_keep_alive, | |
| bool has_in_flight_packets) { | |
| UpdateDeadlines(now, should_keep_alive, has_in_flight_packets); | |
| const QuicTime earliest_deadline = GetEarliestDeadline(); | |
| if (!earliest_deadline.IsInitialized()) { | |
| alarm_->Cancel(); | |
| return; | |
| } | |
| if (earliest_deadline == keep_alive_deadline_) { | |
| // Use 1s granularity for keep-alive time. | |
| alarm_->Update(earliest_deadline, QuicTime::Delta::FromSeconds(1)); | |
| return; | |
| } | |
| alarm_->Update(earliest_deadline, kAlarmGranularity); | |
| } | |
| void QuicPingManager::OnAlarm() { | |
| const QuicTime earliest_deadline = GetEarliestDeadline(); | |
| if (!earliest_deadline.IsInitialized()) { | |
| QUIC_BUG(quic_ping_manager_alarm_fires_unexpectedly) | |
| << "QuicPingManager alarm fires unexpectedly."; | |
| return; | |
| } | |
| // Please note, alarm does not get re-armed here, and we are relying on caller | |
| // to SetAlarm later. | |
| if (earliest_deadline == retransmittable_on_wire_deadline_) { | |
| retransmittable_on_wire_deadline_ = QuicTime::Zero(); | |
| if (GetQuicFlag(quic_max_aggressive_retransmittable_on_wire_ping_count) != | |
| 0) { | |
| ++consecutive_retransmittable_on_wire_count_; | |
| } | |
| ++retransmittable_on_wire_count_; | |
| delegate_->OnRetransmittableOnWireTimeout(); | |
| return; | |
| } | |
| if (earliest_deadline == keep_alive_deadline_) { | |
| keep_alive_deadline_ = QuicTime::Zero(); | |
| delegate_->OnKeepAliveTimeout(); | |
| } | |
| } | |
| void QuicPingManager::Stop() { | |
| alarm_->PermanentCancel(); | |
| retransmittable_on_wire_deadline_ = QuicTime::Zero(); | |
| keep_alive_deadline_ = QuicTime::Zero(); | |
| } | |
| void QuicPingManager::UpdateDeadlines(QuicTime now, bool should_keep_alive, | |
| bool has_in_flight_packets) { | |
| // Reset keep-alive deadline given it will be set later (with left edge | |
| // |now|). | |
| keep_alive_deadline_ = QuicTime::Zero(); | |
| if (perspective_ == Perspective::IS_SERVER && | |
| initial_retransmittable_on_wire_timeout_.IsInfinite()) { | |
| // The PING alarm exists to support two features: | |
| // 1) clients send PINGs every 15s to prevent NAT timeouts, | |
| // 2) both clients and servers can send retransmittable on the wire PINGs | |
| // (ROWP) while ShouldKeepConnectionAlive is true and there is no packets in | |
| // flight. | |
| QUICHE_DCHECK(!retransmittable_on_wire_deadline_.IsInitialized()); | |
| return; | |
| } | |
| if (!should_keep_alive) { | |
| // Don't send a ping unless the application (ie: HTTP/3) says to, usually | |
| // because it is expecting a response from the peer. | |
| retransmittable_on_wire_deadline_ = QuicTime::Zero(); | |
| return; | |
| } | |
| if (perspective_ == Perspective::IS_CLIENT) { | |
| // Clients send 15s PINGs to avoid NATs from timing out. | |
| keep_alive_deadline_ = now + keep_alive_timeout_; | |
| } | |
| if (initial_retransmittable_on_wire_timeout_.IsInfinite() || | |
| has_in_flight_packets || | |
| retransmittable_on_wire_count_ > | |
| GetQuicFlag(quic_max_retransmittable_on_wire_ping_count)) { | |
| // No need to set retransmittable-on-wire timeout. | |
| retransmittable_on_wire_deadline_ = QuicTime::Zero(); | |
| return; | |
| } | |
| QUICHE_DCHECK_LT(initial_retransmittable_on_wire_timeout_, | |
| keep_alive_timeout_); | |
| QuicTime::Delta retransmittable_on_wire_timeout = | |
| initial_retransmittable_on_wire_timeout_; | |
| const int max_aggressive_retransmittable_on_wire_count = | |
| GetQuicFlag(quic_max_aggressive_retransmittable_on_wire_ping_count); | |
| QUICHE_DCHECK_LE(0, max_aggressive_retransmittable_on_wire_count); | |
| if (consecutive_retransmittable_on_wire_count_ > | |
| max_aggressive_retransmittable_on_wire_count) { | |
| // Exponentially back off the timeout if the number of consecutive | |
| // retransmittable on wire pings has exceeds the allowance. | |
| int shift = std::min(consecutive_retransmittable_on_wire_count_ - | |
| max_aggressive_retransmittable_on_wire_count, | |
| kMaxRetransmittableOnWireDelayShift); | |
| retransmittable_on_wire_timeout = | |
| initial_retransmittable_on_wire_timeout_ * (1 << shift); | |
| } | |
| if (retransmittable_on_wire_deadline_.IsInitialized() && | |
| retransmittable_on_wire_deadline_ < | |
| now + retransmittable_on_wire_timeout) { | |
| // Alarm is set to an earlier time. Do not postpone it. | |
| return; | |
| } | |
| retransmittable_on_wire_deadline_ = now + retransmittable_on_wire_timeout; | |
| } | |
| QuicTime QuicPingManager::GetEarliestDeadline() const { | |
| QuicTime earliest_deadline = QuicTime::Zero(); | |
| for (QuicTime t : {retransmittable_on_wire_deadline_, keep_alive_deadline_}) { | |
| if (!t.IsInitialized()) { | |
| continue; | |
| } | |
| if (!earliest_deadline.IsInitialized() || t < earliest_deadline) { | |
| earliest_deadline = t; | |
| } | |
| } | |
| return earliest_deadline; | |
| } | |
| } // namespace quic |