1 // Copyright (c) 2016 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_buffered_packet_store.h"
6
7 #include <string>
8
9 #include "absl/strings/string_view.h"
10 #include "quiche/quic/core/quic_connection_id.h"
11 #include "quiche/quic/core/quic_types.h"
12 #include "quiche/quic/core/quic_versions.h"
13 #include "quiche/quic/platform/api/quic_bug_tracker.h"
14 #include "quiche/quic/platform/api/quic_flags.h"
15
16 namespace quic {
17
18 using BufferedPacket = QuicBufferedPacketStore::BufferedPacket;
19 using BufferedPacketList = QuicBufferedPacketStore::BufferedPacketList;
20 using EnqueuePacketResult = QuicBufferedPacketStore::EnqueuePacketResult;
21
22 // Max number of connections this store can keep track.
23 static const size_t kDefaultMaxConnectionsInStore = 100;
24 // Up to half of the capacity can be used for storing non-CHLO packets.
25 static const size_t kMaxConnectionsWithoutCHLO =
26 kDefaultMaxConnectionsInStore / 2;
27
28 namespace {
29
30 // This alarm removes expired entries in map each time this alarm fires.
31 class ConnectionExpireAlarm : public QuicAlarm::DelegateWithoutContext {
32 public:
ConnectionExpireAlarm(QuicBufferedPacketStore * store)33 explicit ConnectionExpireAlarm(QuicBufferedPacketStore* store)
34 : connection_store_(store) {}
35
OnAlarm()36 void OnAlarm() override { connection_store_->OnExpirationTimeout(); }
37
38 ConnectionExpireAlarm(const ConnectionExpireAlarm&) = delete;
39 ConnectionExpireAlarm& operator=(const ConnectionExpireAlarm&) = delete;
40
41 private:
42 QuicBufferedPacketStore* connection_store_;
43 };
44
45 } // namespace
46
BufferedPacket(std::unique_ptr<QuicReceivedPacket> packet,QuicSocketAddress self_address,QuicSocketAddress peer_address)47 BufferedPacket::BufferedPacket(std::unique_ptr<QuicReceivedPacket> packet,
48 QuicSocketAddress self_address,
49 QuicSocketAddress peer_address)
50 : packet(std::move(packet)),
51 self_address(self_address),
52 peer_address(peer_address) {}
53
54 BufferedPacket::BufferedPacket(BufferedPacket&& other) = default;
55
56 BufferedPacket& BufferedPacket::operator=(BufferedPacket&& other) = default;
57
~BufferedPacket()58 BufferedPacket::~BufferedPacket() {}
59
BufferedPacketList()60 BufferedPacketList::BufferedPacketList()
61 : creation_time(QuicTime::Zero()),
62 ietf_quic(false),
63 version(ParsedQuicVersion::Unsupported()) {}
64
65 BufferedPacketList::BufferedPacketList(BufferedPacketList&& other) = default;
66
67 BufferedPacketList& BufferedPacketList::operator=(BufferedPacketList&& other) =
68 default;
69
~BufferedPacketList()70 BufferedPacketList::~BufferedPacketList() {}
71
QuicBufferedPacketStore(VisitorInterface * visitor,const QuicClock * clock,QuicAlarmFactory * alarm_factory)72 QuicBufferedPacketStore::QuicBufferedPacketStore(
73 VisitorInterface* visitor, const QuicClock* clock,
74 QuicAlarmFactory* alarm_factory)
75 : connection_life_span_(
76 QuicTime::Delta::FromSeconds(kInitialIdleTimeoutSecs)),
77 visitor_(visitor),
78 clock_(clock),
79 expiration_alarm_(
80 alarm_factory->CreateAlarm(new ConnectionExpireAlarm(this))) {}
81
~QuicBufferedPacketStore()82 QuicBufferedPacketStore::~QuicBufferedPacketStore() {
83 if (expiration_alarm_ != nullptr) {
84 expiration_alarm_->PermanentCancel();
85 }
86 }
87
EnqueuePacket(QuicConnectionId connection_id,bool ietf_quic,const QuicReceivedPacket & packet,QuicSocketAddress self_address,QuicSocketAddress peer_address,const ParsedQuicVersion & version,absl::optional<ParsedClientHello> parsed_chlo)88 EnqueuePacketResult QuicBufferedPacketStore::EnqueuePacket(
89 QuicConnectionId connection_id, bool ietf_quic,
90 const QuicReceivedPacket& packet, QuicSocketAddress self_address,
91 QuicSocketAddress peer_address, const ParsedQuicVersion& version,
92 absl::optional<ParsedClientHello> parsed_chlo) {
93 const bool is_chlo = parsed_chlo.has_value();
94 QUIC_BUG_IF(quic_bug_12410_1, !GetQuicFlag(quic_allow_chlo_buffering))
95 << "Shouldn't buffer packets if disabled via flag.";
96 QUIC_BUG_IF(quic_bug_12410_2,
97 is_chlo && connections_with_chlo_.contains(connection_id))
98 << "Shouldn't buffer duplicated CHLO on connection " << connection_id;
99 QUIC_BUG_IF(quic_bug_12410_4, is_chlo && !version.IsKnown())
100 << "Should have version for CHLO packet.";
101
102 const bool is_first_packet = !undecryptable_packets_.contains(connection_id);
103 if (is_first_packet) {
104 if (ShouldNotBufferPacket(is_chlo)) {
105 // Drop the packet if the upper limit of undecryptable packets has been
106 // reached or the whole capacity of the store has been reached.
107 return TOO_MANY_CONNECTIONS;
108 }
109 undecryptable_packets_.emplace(
110 std::make_pair(connection_id, BufferedPacketList()));
111 undecryptable_packets_.back().second.ietf_quic = ietf_quic;
112 undecryptable_packets_.back().second.version = version;
113 }
114 QUICHE_CHECK(undecryptable_packets_.contains(connection_id));
115 BufferedPacketList& queue =
116 undecryptable_packets_.find(connection_id)->second;
117
118 if (!is_chlo) {
119 // If current packet is not CHLO, it might not be buffered because store
120 // only buffers certain number of undecryptable packets per connection.
121 size_t num_non_chlo_packets = connections_with_chlo_.contains(connection_id)
122 ? (queue.buffered_packets.size() - 1)
123 : queue.buffered_packets.size();
124 if (num_non_chlo_packets >= kDefaultMaxUndecryptablePackets) {
125 // If there are kMaxBufferedPacketsPerConnection packets buffered up for
126 // this connection, drop the current packet.
127 return TOO_MANY_PACKETS;
128 }
129 }
130
131 if (queue.buffered_packets.empty()) {
132 // If this is the first packet arrived on a new connection, initialize the
133 // creation time.
134 queue.creation_time = clock_->ApproximateNow();
135 }
136
137 BufferedPacket new_entry(std::unique_ptr<QuicReceivedPacket>(packet.Clone()),
138 self_address, peer_address);
139 if (is_chlo) {
140 // Add CHLO to the beginning of buffered packets so that it can be delivered
141 // first later.
142 queue.buffered_packets.push_front(std::move(new_entry));
143 queue.parsed_chlo = std::move(parsed_chlo);
144 connections_with_chlo_[connection_id] = false; // Dummy value.
145 // Set the version of buffered packets of this connection on CHLO.
146 queue.version = version;
147 } else {
148 // Buffer non-CHLO packets in arrival order.
149 queue.buffered_packets.push_back(std::move(new_entry));
150
151 // Attempt to parse multi-packet TLS CHLOs.
152 if (is_first_packet) {
153 queue.tls_chlo_extractor.IngestPacket(version, packet);
154 // Since this is the first packet and it's not a CHLO, the
155 // TlsChloExtractor should not have the entire CHLO.
156 QUIC_BUG_IF(quic_bug_12410_5,
157 queue.tls_chlo_extractor.HasParsedFullChlo())
158 << "First packet in list should not contain full CHLO";
159 }
160 // TODO(b/154857081) Reorder CHLO packets ahead of other ones.
161 }
162
163 MaybeSetExpirationAlarm();
164 return SUCCESS;
165 }
166
HasBufferedPackets(QuicConnectionId connection_id) const167 bool QuicBufferedPacketStore::HasBufferedPackets(
168 QuicConnectionId connection_id) const {
169 return undecryptable_packets_.contains(connection_id);
170 }
171
HasChlosBuffered() const172 bool QuicBufferedPacketStore::HasChlosBuffered() const {
173 return !connections_with_chlo_.empty();
174 }
175
DeliverPackets(QuicConnectionId connection_id)176 BufferedPacketList QuicBufferedPacketStore::DeliverPackets(
177 QuicConnectionId connection_id) {
178 BufferedPacketList packets_to_deliver;
179 auto it = undecryptable_packets_.find(connection_id);
180 if (it != undecryptable_packets_.end()) {
181 packets_to_deliver = std::move(it->second);
182 undecryptable_packets_.erase(connection_id);
183 std::list<BufferedPacket> initial_packets;
184 std::list<BufferedPacket> other_packets;
185 for (auto& packet : packets_to_deliver.buffered_packets) {
186 QuicLongHeaderType long_packet_type = INVALID_PACKET_TYPE;
187 PacketHeaderFormat unused_format;
188 bool unused_version_flag;
189 bool unused_use_length_prefix;
190 QuicVersionLabel unused_version_label;
191 ParsedQuicVersion unused_parsed_version = UnsupportedQuicVersion();
192 QuicConnectionId unused_destination_connection_id;
193 QuicConnectionId unused_source_connection_id;
194 absl::optional<absl::string_view> unused_retry_token;
195 std::string unused_detailed_error;
196
197 // We don't need to pass |generator| because we already got the correct
198 // connection ID length when we buffered the packet and indexed by
199 // connection ID.
200 QuicErrorCode error_code = QuicFramer::ParsePublicHeaderDispatcher(
201 *packet.packet, connection_id.length(), &unused_format,
202 &long_packet_type, &unused_version_flag, &unused_use_length_prefix,
203 &unused_version_label, &unused_parsed_version,
204 &unused_destination_connection_id, &unused_source_connection_id,
205 &unused_retry_token, &unused_detailed_error);
206
207 if (error_code == QUIC_NO_ERROR && long_packet_type == INITIAL) {
208 initial_packets.push_back(std::move(packet));
209 } else {
210 other_packets.push_back(std::move(packet));
211 }
212 }
213
214 initial_packets.splice(initial_packets.end(), other_packets);
215 packets_to_deliver.buffered_packets = std::move(initial_packets);
216 }
217 return packets_to_deliver;
218 }
219
DiscardPackets(QuicConnectionId connection_id)220 void QuicBufferedPacketStore::DiscardPackets(QuicConnectionId connection_id) {
221 undecryptable_packets_.erase(connection_id);
222 connections_with_chlo_.erase(connection_id);
223 }
224
DiscardAllPackets()225 void QuicBufferedPacketStore::DiscardAllPackets() {
226 undecryptable_packets_.clear();
227 connections_with_chlo_.clear();
228 expiration_alarm_->Cancel();
229 }
230
OnExpirationTimeout()231 void QuicBufferedPacketStore::OnExpirationTimeout() {
232 QuicTime expiration_time = clock_->ApproximateNow() - connection_life_span_;
233 while (!undecryptable_packets_.empty()) {
234 auto& entry = undecryptable_packets_.front();
235 if (entry.second.creation_time > expiration_time) {
236 break;
237 }
238 QuicConnectionId connection_id = entry.first;
239 visitor_->OnExpiredPackets(connection_id, std::move(entry.second));
240 undecryptable_packets_.pop_front();
241 connections_with_chlo_.erase(connection_id);
242 }
243 if (!undecryptable_packets_.empty()) {
244 MaybeSetExpirationAlarm();
245 }
246 }
247
MaybeSetExpirationAlarm()248 void QuicBufferedPacketStore::MaybeSetExpirationAlarm() {
249 if (!expiration_alarm_->IsSet()) {
250 expiration_alarm_->Set(clock_->ApproximateNow() + connection_life_span_);
251 }
252 }
253
ShouldNotBufferPacket(bool is_chlo)254 bool QuicBufferedPacketStore::ShouldNotBufferPacket(bool is_chlo) {
255 bool is_store_full =
256 undecryptable_packets_.size() >= kDefaultMaxConnectionsInStore;
257
258 if (is_chlo) {
259 return is_store_full;
260 }
261
262 size_t num_connections_without_chlo =
263 undecryptable_packets_.size() - connections_with_chlo_.size();
264 bool reach_non_chlo_limit =
265 num_connections_without_chlo >= kMaxConnectionsWithoutCHLO;
266
267 return is_store_full || reach_non_chlo_limit;
268 }
269
DeliverPacketsForNextConnection(QuicConnectionId * connection_id)270 BufferedPacketList QuicBufferedPacketStore::DeliverPacketsForNextConnection(
271 QuicConnectionId* connection_id) {
272 if (connections_with_chlo_.empty()) {
273 // Returns empty list if no CHLO has been buffered.
274 return BufferedPacketList();
275 }
276 *connection_id = connections_with_chlo_.front().first;
277 connections_with_chlo_.pop_front();
278
279 BufferedPacketList packets = DeliverPackets(*connection_id);
280 QUICHE_DCHECK(!packets.buffered_packets.empty() &&
281 packets.parsed_chlo.has_value())
282 << "Try to deliver connectons without CHLO. # packets:"
283 << packets.buffered_packets.size()
284 << ", has_parsed_chlo:" << packets.parsed_chlo.has_value();
285 return packets;
286 }
287
HasChloForConnection(QuicConnectionId connection_id)288 bool QuicBufferedPacketStore::HasChloForConnection(
289 QuicConnectionId connection_id) {
290 return connections_with_chlo_.contains(connection_id);
291 }
292
IngestPacketForTlsChloExtraction(const QuicConnectionId & connection_id,const ParsedQuicVersion & version,const QuicReceivedPacket & packet,std::vector<std::string> * out_alpns,std::string * out_sni,bool * out_resumption_attempted,bool * out_early_data_attempted,absl::optional<uint8_t> * tls_alert)293 bool QuicBufferedPacketStore::IngestPacketForTlsChloExtraction(
294 const QuicConnectionId& connection_id, const ParsedQuicVersion& version,
295 const QuicReceivedPacket& packet, std::vector<std::string>* out_alpns,
296 std::string* out_sni, bool* out_resumption_attempted,
297 bool* out_early_data_attempted, absl::optional<uint8_t>* tls_alert) {
298 QUICHE_DCHECK_NE(out_alpns, nullptr);
299 QUICHE_DCHECK_NE(out_sni, nullptr);
300 QUICHE_DCHECK_NE(tls_alert, nullptr);
301 QUICHE_DCHECK_EQ(version.handshake_protocol, PROTOCOL_TLS1_3);
302 auto it = undecryptable_packets_.find(connection_id);
303 if (it == undecryptable_packets_.end()) {
304 QUIC_BUG(quic_bug_10838_1)
305 << "Cannot ingest packet for unknown connection ID " << connection_id;
306 return false;
307 }
308 it->second.tls_chlo_extractor.IngestPacket(version, packet);
309 if (!it->second.tls_chlo_extractor.HasParsedFullChlo()) {
310 *tls_alert = it->second.tls_chlo_extractor.tls_alert();
311 return false;
312 }
313 const TlsChloExtractor& tls_chlo_extractor = it->second.tls_chlo_extractor;
314 *out_alpns = tls_chlo_extractor.alpns();
315 *out_sni = tls_chlo_extractor.server_name();
316 *out_resumption_attempted = tls_chlo_extractor.resumption_attempted();
317 *out_early_data_attempted = tls_chlo_extractor.early_data_attempted();
318 return true;
319 }
320
321 } // namespace quic
322