| // Copyright (c) 2016 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_buffered_packet_store.h" |
| |
| #include <string> |
| |
| #include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_flags.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_map_util.h" |
| |
| namespace quic { |
| |
| typedef QuicBufferedPacketStore::BufferedPacket BufferedPacket; |
| typedef QuicBufferedPacketStore::BufferedPacketList BufferedPacketList; |
| typedef QuicBufferedPacketStore::EnqueuePacketResult EnqueuePacketResult; |
| |
| // Max number of connections this store can keep track. |
| static const size_t kDefaultMaxConnectionsInStore = 100; |
| // Up to half of the capacity can be used for storing non-CHLO packets. |
| static const size_t kMaxConnectionsWithoutCHLO = |
| kDefaultMaxConnectionsInStore / 2; |
| |
| namespace { |
| |
| // This alarm removes expired entries in map each time this alarm fires. |
| class ConnectionExpireAlarm : public QuicAlarm::Delegate { |
| public: |
| explicit ConnectionExpireAlarm(QuicBufferedPacketStore* store) |
| : connection_store_(store) {} |
| |
| void OnAlarm() override { connection_store_->OnExpirationTimeout(); } |
| |
| ConnectionExpireAlarm(const ConnectionExpireAlarm&) = delete; |
| ConnectionExpireAlarm& operator=(const ConnectionExpireAlarm&) = delete; |
| |
| private: |
| QuicBufferedPacketStore* connection_store_; |
| }; |
| |
| } // namespace |
| |
| BufferedPacket::BufferedPacket(std::unique_ptr<QuicReceivedPacket> packet, |
| QuicSocketAddress self_address, |
| QuicSocketAddress peer_address) |
| : packet(std::move(packet)), |
| self_address(self_address), |
| peer_address(peer_address) {} |
| |
| BufferedPacket::BufferedPacket(BufferedPacket&& other) = default; |
| |
| BufferedPacket& BufferedPacket::operator=(BufferedPacket&& other) = default; |
| |
| BufferedPacket::~BufferedPacket() {} |
| |
| BufferedPacketList::BufferedPacketList() |
| : creation_time(QuicTime::Zero()), |
| ietf_quic(false), |
| version(PROTOCOL_UNSUPPORTED, QUIC_VERSION_UNSUPPORTED) {} |
| |
| BufferedPacketList::BufferedPacketList(BufferedPacketList&& other) = default; |
| |
| BufferedPacketList& BufferedPacketList::operator=(BufferedPacketList&& other) = |
| default; |
| |
| BufferedPacketList::~BufferedPacketList() {} |
| |
| QuicBufferedPacketStore::QuicBufferedPacketStore( |
| VisitorInterface* visitor, |
| const QuicClock* clock, |
| QuicAlarmFactory* alarm_factory) |
| : connection_life_span_( |
| QuicTime::Delta::FromSeconds(kInitialIdleTimeoutSecs)), |
| visitor_(visitor), |
| clock_(clock), |
| expiration_alarm_( |
| alarm_factory->CreateAlarm(new ConnectionExpireAlarm(this))) {} |
| |
| QuicBufferedPacketStore::~QuicBufferedPacketStore() {} |
| |
| EnqueuePacketResult QuicBufferedPacketStore::EnqueuePacket( |
| QuicConnectionId connection_id, |
| bool ietf_quic, |
| const QuicReceivedPacket& packet, |
| QuicSocketAddress self_address, |
| QuicSocketAddress peer_address, |
| bool is_chlo, |
| const std::string& alpn, |
| const ParsedQuicVersion& version) { |
| QUIC_BUG_IF(!GetQuicFlag(FLAGS_quic_allow_chlo_buffering)) |
| << "Shouldn't buffer packets if disabled via flag."; |
| QUIC_BUG_IF(is_chlo && QuicContainsKey(connections_with_chlo_, connection_id)) |
| << "Shouldn't buffer duplicated CHLO on connection " << connection_id; |
| QUIC_BUG_IF(!is_chlo && !alpn.empty()) |
| << "Shouldn't have an ALPN defined for a non-CHLO packet."; |
| QUIC_BUG_IF(is_chlo && version.transport_version == QUIC_VERSION_UNSUPPORTED) |
| << "Should have version for CHLO packet."; |
| |
| if (!QuicContainsKey(undecryptable_packets_, connection_id) && |
| ShouldBufferPacket(is_chlo)) { |
| // Drop the packet if the upper limit of undecryptable packets has been |
| // reached or the whole capacity of the store has been reached. |
| return TOO_MANY_CONNECTIONS; |
| } else if (!QuicContainsKey(undecryptable_packets_, connection_id)) { |
| undecryptable_packets_.emplace( |
| std::make_pair(connection_id, BufferedPacketList())); |
| undecryptable_packets_.back().second.ietf_quic = ietf_quic; |
| undecryptable_packets_.back().second.version = version; |
| } |
| CHECK(QuicContainsKey(undecryptable_packets_, connection_id)); |
| BufferedPacketList& queue = |
| undecryptable_packets_.find(connection_id)->second; |
| |
| if (!is_chlo) { |
| // If current packet is not CHLO, it might not be buffered because store |
| // only buffers certain number of undecryptable packets per connection. |
| size_t num_non_chlo_packets = |
| QuicContainsKey(connections_with_chlo_, connection_id) |
| ? (queue.buffered_packets.size() - 1) |
| : queue.buffered_packets.size(); |
| if (num_non_chlo_packets >= kDefaultMaxUndecryptablePackets) { |
| // If there are kMaxBufferedPacketsPerConnection packets buffered up for |
| // this connection, drop the current packet. |
| return TOO_MANY_PACKETS; |
| } |
| } |
| |
| if (queue.buffered_packets.empty()) { |
| // If this is the first packet arrived on a new connection, initialize the |
| // creation time. |
| queue.creation_time = clock_->ApproximateNow(); |
| } |
| |
| BufferedPacket new_entry(std::unique_ptr<QuicReceivedPacket>(packet.Clone()), |
| self_address, peer_address); |
| if (is_chlo) { |
| // Add CHLO to the beginning of buffered packets so that it can be delivered |
| // first later. |
| queue.buffered_packets.push_front(std::move(new_entry)); |
| queue.alpn = alpn; |
| connections_with_chlo_[connection_id] = false; // Dummy value. |
| // Set the version of buffered packets of this connection on CHLO. |
| queue.version = version; |
| } else { |
| // Buffer non-CHLO packets in arrival order. |
| queue.buffered_packets.push_back(std::move(new_entry)); |
| } |
| MaybeSetExpirationAlarm(); |
| return SUCCESS; |
| } |
| |
| bool QuicBufferedPacketStore::HasBufferedPackets( |
| QuicConnectionId connection_id) const { |
| return QuicContainsKey(undecryptable_packets_, connection_id); |
| } |
| |
| bool QuicBufferedPacketStore::HasChlosBuffered() const { |
| return !connections_with_chlo_.empty(); |
| } |
| |
| BufferedPacketList QuicBufferedPacketStore::DeliverPackets( |
| QuicConnectionId connection_id) { |
| BufferedPacketList packets_to_deliver; |
| auto it = undecryptable_packets_.find(connection_id); |
| if (it != undecryptable_packets_.end()) { |
| packets_to_deliver = std::move(it->second); |
| undecryptable_packets_.erase(connection_id); |
| } |
| return packets_to_deliver; |
| } |
| |
| void QuicBufferedPacketStore::DiscardPackets(QuicConnectionId connection_id) { |
| undecryptable_packets_.erase(connection_id); |
| connections_with_chlo_.erase(connection_id); |
| } |
| |
| void QuicBufferedPacketStore::OnExpirationTimeout() { |
| QuicTime expiration_time = clock_->ApproximateNow() - connection_life_span_; |
| while (!undecryptable_packets_.empty()) { |
| auto& entry = undecryptable_packets_.front(); |
| if (entry.second.creation_time > expiration_time) { |
| break; |
| } |
| QuicConnectionId connection_id = entry.first; |
| visitor_->OnExpiredPackets(connection_id, std::move(entry.second)); |
| undecryptable_packets_.pop_front(); |
| connections_with_chlo_.erase(connection_id); |
| } |
| if (!undecryptable_packets_.empty()) { |
| MaybeSetExpirationAlarm(); |
| } |
| } |
| |
| void QuicBufferedPacketStore::MaybeSetExpirationAlarm() { |
| if (!expiration_alarm_->IsSet()) { |
| expiration_alarm_->Set(clock_->ApproximateNow() + connection_life_span_); |
| } |
| } |
| |
| bool QuicBufferedPacketStore::ShouldBufferPacket(bool is_chlo) { |
| bool is_store_full = |
| undecryptable_packets_.size() >= kDefaultMaxConnectionsInStore; |
| |
| if (is_chlo) { |
| return is_store_full; |
| } |
| |
| size_t num_connections_without_chlo = |
| undecryptable_packets_.size() - connections_with_chlo_.size(); |
| bool reach_non_chlo_limit = |
| num_connections_without_chlo >= kMaxConnectionsWithoutCHLO; |
| |
| return is_store_full || reach_non_chlo_limit; |
| } |
| |
| BufferedPacketList QuicBufferedPacketStore::DeliverPacketsForNextConnection( |
| QuicConnectionId* connection_id) { |
| if (connections_with_chlo_.empty()) { |
| // Returns empty list if no CHLO has been buffered. |
| return BufferedPacketList(); |
| } |
| *connection_id = connections_with_chlo_.front().first; |
| connections_with_chlo_.pop_front(); |
| |
| BufferedPacketList packets = DeliverPackets(*connection_id); |
| DCHECK(!packets.buffered_packets.empty()) |
| << "Try to deliver connectons without CHLO"; |
| return packets; |
| } |
| |
| bool QuicBufferedPacketStore::HasChloForConnection( |
| QuicConnectionId connection_id) { |
| return QuicContainsKey(connections_with_chlo_, connection_id); |
| } |
| |
| } // namespace quic |