1 // Copyright (c) 2022 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "quiche/quic/core/quic_ping_manager.h"
6
7 #include "quiche/quic/platform/api/quic_flags.h"
8
9 namespace quic {
10
11 namespace {
12
13 // Maximum shift used to calculate retransmittable on wire timeout. For 200ms
14 // initial retransmittable on wire delay, this would get a maximum of 200ms * (1
15 // << 10) = 204.8s
16 const int kMaxRetransmittableOnWireDelayShift = 10;
17
18 class AlarmDelegate : public QuicAlarm::DelegateWithContext {
19 public:
AlarmDelegate(QuicPingManager * manager,QuicConnectionContext * context)20 explicit AlarmDelegate(QuicPingManager* manager,
21 QuicConnectionContext* context)
22 : QuicAlarm::DelegateWithContext(context), manager_(manager) {}
23 AlarmDelegate(const AlarmDelegate&) = delete;
24 AlarmDelegate& operator=(const AlarmDelegate&) = delete;
25
OnAlarm()26 void OnAlarm() override { manager_->OnAlarm(); }
27
28 private:
29 QuicPingManager* manager_;
30 };
31
32 } // namespace
33
QuicPingManager(Perspective perspective,Delegate * delegate,QuicConnectionArena * arena,QuicAlarmFactory * alarm_factory,QuicConnectionContext * context)34 QuicPingManager::QuicPingManager(Perspective perspective, Delegate* delegate,
35 QuicConnectionArena* arena,
36 QuicAlarmFactory* alarm_factory,
37 QuicConnectionContext* context)
38 : perspective_(perspective),
39 delegate_(delegate),
40 alarm_(alarm_factory->CreateAlarm(
41 arena->New<AlarmDelegate>(this, context), arena)) {}
42
SetAlarm(QuicTime now,bool should_keep_alive,bool has_in_flight_packets)43 void QuicPingManager::SetAlarm(QuicTime now, bool should_keep_alive,
44 bool has_in_flight_packets) {
45 UpdateDeadlines(now, should_keep_alive, has_in_flight_packets);
46 const QuicTime earliest_deadline = GetEarliestDeadline();
47 if (!earliest_deadline.IsInitialized()) {
48 alarm_->Cancel();
49 return;
50 }
51 if (earliest_deadline == keep_alive_deadline_) {
52 // Use 1s granularity for keep-alive time.
53 alarm_->Update(earliest_deadline, QuicTime::Delta::FromSeconds(1));
54 return;
55 }
56 alarm_->Update(earliest_deadline, kAlarmGranularity);
57 }
58
OnAlarm()59 void QuicPingManager::OnAlarm() {
60 const QuicTime earliest_deadline = GetEarliestDeadline();
61 if (!earliest_deadline.IsInitialized()) {
62 QUIC_BUG(quic_ping_manager_alarm_fires_unexpectedly)
63 << "QuicPingManager alarm fires unexpectedly.";
64 return;
65 }
66 // Please note, alarm does not get re-armed here, and we are relying on caller
67 // to SetAlarm later.
68 if (earliest_deadline == retransmittable_on_wire_deadline_) {
69 retransmittable_on_wire_deadline_ = QuicTime::Zero();
70 if (GetQuicFlag(quic_max_aggressive_retransmittable_on_wire_ping_count) !=
71 0) {
72 ++consecutive_retransmittable_on_wire_count_;
73 }
74 ++retransmittable_on_wire_count_;
75 delegate_->OnRetransmittableOnWireTimeout();
76 return;
77 }
78 if (earliest_deadline == keep_alive_deadline_) {
79 keep_alive_deadline_ = QuicTime::Zero();
80 delegate_->OnKeepAliveTimeout();
81 }
82 }
83
Stop()84 void QuicPingManager::Stop() {
85 alarm_->PermanentCancel();
86 retransmittable_on_wire_deadline_ = QuicTime::Zero();
87 keep_alive_deadline_ = QuicTime::Zero();
88 }
89
UpdateDeadlines(QuicTime now,bool should_keep_alive,bool has_in_flight_packets)90 void QuicPingManager::UpdateDeadlines(QuicTime now, bool should_keep_alive,
91 bool has_in_flight_packets) {
92 // Reset keep-alive deadline given it will be set later (with left edge
93 // |now|).
94 keep_alive_deadline_ = QuicTime::Zero();
95 if (perspective_ == Perspective::IS_SERVER &&
96 initial_retransmittable_on_wire_timeout_.IsInfinite()) {
97 // The PING alarm exists to support two features:
98 // 1) clients send PINGs every 15s to prevent NAT timeouts,
99 // 2) both clients and servers can send retransmittable on the wire PINGs
100 // (ROWP) while ShouldKeepConnectionAlive is true and there is no packets in
101 // flight.
102 QUICHE_DCHECK(!retransmittable_on_wire_deadline_.IsInitialized());
103 return;
104 }
105 if (!should_keep_alive) {
106 // Don't send a ping unless the application (ie: HTTP/3) says to, usually
107 // because it is expecting a response from the peer.
108 retransmittable_on_wire_deadline_ = QuicTime::Zero();
109 return;
110 }
111 if (perspective_ == Perspective::IS_CLIENT) {
112 // Clients send 15s PINGs to avoid NATs from timing out.
113 keep_alive_deadline_ = now + keep_alive_timeout_;
114 }
115 if (initial_retransmittable_on_wire_timeout_.IsInfinite() ||
116 has_in_flight_packets ||
117 retransmittable_on_wire_count_ >
118 GetQuicFlag(quic_max_retransmittable_on_wire_ping_count)) {
119 // No need to set retransmittable-on-wire timeout.
120 retransmittable_on_wire_deadline_ = QuicTime::Zero();
121 return;
122 }
123
124 QUICHE_DCHECK_LT(initial_retransmittable_on_wire_timeout_,
125 keep_alive_timeout_);
126 QuicTime::Delta retransmittable_on_wire_timeout =
127 initial_retransmittable_on_wire_timeout_;
128 const int max_aggressive_retransmittable_on_wire_count =
129 GetQuicFlag(quic_max_aggressive_retransmittable_on_wire_ping_count);
130 QUICHE_DCHECK_LE(0, max_aggressive_retransmittable_on_wire_count);
131 if (consecutive_retransmittable_on_wire_count_ >
132 max_aggressive_retransmittable_on_wire_count) {
133 // Exponentially back off the timeout if the number of consecutive
134 // retransmittable on wire pings has exceeds the allowance.
135 int shift = std::min(consecutive_retransmittable_on_wire_count_ -
136 max_aggressive_retransmittable_on_wire_count,
137 kMaxRetransmittableOnWireDelayShift);
138 retransmittable_on_wire_timeout =
139 initial_retransmittable_on_wire_timeout_ * (1 << shift);
140 }
141 if (retransmittable_on_wire_deadline_.IsInitialized() &&
142 retransmittable_on_wire_deadline_ <
143 now + retransmittable_on_wire_timeout) {
144 // Alarm is set to an earlier time. Do not postpone it.
145 return;
146 }
147 retransmittable_on_wire_deadline_ = now + retransmittable_on_wire_timeout;
148 }
149
GetEarliestDeadline() const150 QuicTime QuicPingManager::GetEarliestDeadline() const {
151 QuicTime earliest_deadline = QuicTime::Zero();
152 for (QuicTime t : {retransmittable_on_wire_deadline_, keep_alive_deadline_}) {
153 if (!t.IsInitialized()) {
154 continue;
155 }
156 if (!earliest_deadline.IsInitialized() || t < earliest_deadline) {
157 earliest_deadline = t;
158 }
159 }
160 return earliest_deadline;
161 }
162
163 } // namespace quic
164