| // Copyright 2014 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/quic/quic_time_wait_list_manager.h" |
| |
| #include <errno.h> |
| |
| #include "base/containers/hash_tables.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/stl_util.h" |
| #include "net/base/ip_endpoint.h" |
| #include "net/quic/crypto/crypto_protocol.h" |
| #include "net/quic/crypto/quic_decrypter.h" |
| #include "net/quic/crypto/quic_encrypter.h" |
| #include "net/quic/quic_clock.h" |
| #include "net/quic/quic_connection_helper.h" |
| #include "net/quic/quic_flags.h" |
| #include "net/quic/quic_framer.h" |
| #include "net/quic/quic_protocol.h" |
| #include "net/quic/quic_server_session.h" |
| #include "net/quic/quic_utils.h" |
| |
| using base::StringPiece; |
| using std::make_pair; |
| |
| namespace net { |
| |
| // A very simple alarm that just informs the QuicTimeWaitListManager to clean |
| // up old connection_ids. This alarm should be unregistered and deleted before |
| // the QuicTimeWaitListManager is deleted. |
| class ConnectionIdCleanUpAlarm : public QuicAlarm::Delegate { |
| public: |
| explicit ConnectionIdCleanUpAlarm( |
| QuicTimeWaitListManager* time_wait_list_manager) |
| : time_wait_list_manager_(time_wait_list_manager) { |
| } |
| |
| QuicTime OnAlarm() override { |
| time_wait_list_manager_->CleanUpOldConnectionIds(); |
| // Let the time wait manager register the alarm at appropriate time. |
| return QuicTime::Zero(); |
| } |
| |
| private: |
| // Not owned. |
| QuicTimeWaitListManager* time_wait_list_manager_; |
| }; |
| |
| // This class stores pending public reset packets to be sent to clients. |
| // server_address - server address on which a packet what was received for |
| // a connection_id in time wait state. |
| // client_address - address of the client that sent that packet. Needed to send |
| // the public reset packet back to the client. |
| // packet - the pending public reset packet that is to be sent to the client. |
| // created instance takes the ownership of this packet. |
| class QuicTimeWaitListManager::QueuedPacket { |
| public: |
| QueuedPacket(const IPEndPoint& server_address, |
| const IPEndPoint& client_address, |
| QuicEncryptedPacket* packet) |
| : server_address_(server_address), |
| client_address_(client_address), |
| packet_(packet) { |
| } |
| |
| const IPEndPoint& server_address() const { return server_address_; } |
| const IPEndPoint& client_address() const { return client_address_; } |
| QuicEncryptedPacket* packet() { return packet_.get(); } |
| |
| private: |
| const IPEndPoint server_address_; |
| const IPEndPoint client_address_; |
| scoped_ptr<QuicEncryptedPacket> packet_; |
| |
| DISALLOW_COPY_AND_ASSIGN(QueuedPacket); |
| }; |
| |
| QuicTimeWaitListManager::QuicTimeWaitListManager( |
| QuicPacketWriter* writer, |
| QuicServerSessionVisitor* visitor, |
| QuicConnectionHelperInterface* helper, |
| const QuicVersionVector& supported_versions) |
| : helper_(helper), |
| kTimeWaitPeriod_( |
| QuicTime::Delta::FromSeconds(FLAGS_quic_time_wait_list_seconds)), |
| connection_id_clean_up_alarm_( |
| helper_->CreateAlarm(new ConnectionIdCleanUpAlarm(this))), |
| writer_(writer), |
| visitor_(visitor) { |
| SetConnectionIdCleanUpAlarm(); |
| } |
| |
| QuicTimeWaitListManager::~QuicTimeWaitListManager() { |
| connection_id_clean_up_alarm_->Cancel(); |
| STLDeleteElements(&pending_packets_queue_); |
| for (ConnectionIdMap::iterator it = connection_id_map_.begin(); |
| it != connection_id_map_.end(); |
| ++it) { |
| delete it->second.close_packet; |
| } |
| } |
| |
| void QuicTimeWaitListManager::AddConnectionIdToTimeWait( |
| QuicConnectionId connection_id, |
| QuicVersion version, |
| QuicEncryptedPacket* close_packet) { |
| int num_packets = 0; |
| ConnectionIdMap::iterator it = connection_id_map_.find(connection_id); |
| const bool new_connection_id = it == connection_id_map_.end(); |
| if (!new_connection_id) { // Replace record if it is reinserted. |
| num_packets = it->second.num_packets; |
| delete it->second.close_packet; |
| connection_id_map_.erase(it); |
| } |
| TrimTimeWaitListIfNeeded(); |
| if (FLAGS_quic_limit_time_wait_list_size) { |
| DCHECK_LT(num_connections(), |
| static_cast<size_t>(FLAGS_quic_time_wait_list_max_connections)); |
| } |
| ConnectionIdData data(num_packets, |
| version, |
| helper_->GetClock()->ApproximateNow(), |
| close_packet); |
| connection_id_map_.insert(make_pair(connection_id, data)); |
| if (new_connection_id) { |
| visitor_->OnConnectionAddedToTimeWaitList(connection_id); |
| } |
| } |
| |
| bool QuicTimeWaitListManager::IsConnectionIdInTimeWait( |
| QuicConnectionId connection_id) const { |
| return ContainsKey(connection_id_map_, connection_id); |
| } |
| |
| QuicVersion QuicTimeWaitListManager::GetQuicVersionFromConnectionId( |
| QuicConnectionId connection_id) { |
| ConnectionIdMap::iterator it = connection_id_map_.find(connection_id); |
| DCHECK(it != connection_id_map_.end()); |
| return (it->second).version; |
| } |
| |
| void QuicTimeWaitListManager::OnCanWrite() { |
| while (!pending_packets_queue_.empty()) { |
| QueuedPacket* queued_packet = pending_packets_queue_.front(); |
| if (!WriteToWire(queued_packet)) { |
| return; |
| } |
| pending_packets_queue_.pop_front(); |
| delete queued_packet; |
| } |
| } |
| |
| void QuicTimeWaitListManager::ProcessPacket( |
| const IPEndPoint& server_address, |
| const IPEndPoint& client_address, |
| QuicConnectionId connection_id, |
| QuicPacketSequenceNumber sequence_number, |
| const QuicEncryptedPacket& /*packet*/) { |
| DCHECK(IsConnectionIdInTimeWait(connection_id)); |
| DVLOG(1) << "Processing " << connection_id << " in time wait state."; |
| // TODO(satyamshekhar): Think about handling packets from different client |
| // addresses. |
| ConnectionIdMap::iterator it = connection_id_map_.find(connection_id); |
| DCHECK(it != connection_id_map_.end()); |
| // Increment the received packet count. |
| ++((it->second).num_packets); |
| if (!ShouldSendResponse((it->second).num_packets)) { |
| return; |
| } |
| if (it->second.close_packet) { |
| QueuedPacket* queued_packet = |
| new QueuedPacket(server_address, |
| client_address, |
| it->second.close_packet->Clone()); |
| // Takes ownership of the packet. |
| SendOrQueuePacket(queued_packet); |
| } else { |
| SendPublicReset(server_address, |
| client_address, |
| connection_id, |
| sequence_number); |
| } |
| } |
| |
| // Returns true if the number of packets received for this connection_id is a |
| // power of 2 to throttle the number of public reset packets we send to a |
| // client. |
| bool QuicTimeWaitListManager::ShouldSendResponse(int received_packet_count) { |
| return (received_packet_count & (received_packet_count - 1)) == 0; |
| } |
| |
| void QuicTimeWaitListManager::SendPublicReset( |
| const IPEndPoint& server_address, |
| const IPEndPoint& client_address, |
| QuicConnectionId connection_id, |
| QuicPacketSequenceNumber rejected_sequence_number) { |
| QuicPublicResetPacket packet; |
| packet.public_header.connection_id = connection_id; |
| packet.public_header.reset_flag = true; |
| packet.public_header.version_flag = false; |
| packet.rejected_sequence_number = rejected_sequence_number; |
| // TODO(satyamshekhar): generate a valid nonce for this connection_id. |
| packet.nonce_proof = 1010101; |
| packet.client_address = client_address; |
| QueuedPacket* queued_packet = new QueuedPacket( |
| server_address, |
| client_address, |
| BuildPublicReset(packet)); |
| // Takes ownership of the packet. |
| SendOrQueuePacket(queued_packet); |
| } |
| |
| QuicEncryptedPacket* QuicTimeWaitListManager::BuildPublicReset( |
| const QuicPublicResetPacket& packet) { |
| return QuicFramer::BuildPublicResetPacket(packet); |
| } |
| |
| // Either sends the packet and deletes it or makes pending queue the |
| // owner of the packet. |
| void QuicTimeWaitListManager::SendOrQueuePacket(QueuedPacket* packet) { |
| if (WriteToWire(packet)) { |
| delete packet; |
| } else { |
| // pending_packets_queue takes the ownership of the queued packet. |
| pending_packets_queue_.push_back(packet); |
| } |
| } |
| |
| bool QuicTimeWaitListManager::WriteToWire(QueuedPacket* queued_packet) { |
| if (writer_->IsWriteBlocked()) { |
| visitor_->OnWriteBlocked(this); |
| return false; |
| } |
| WriteResult result = writer_->WritePacket( |
| queued_packet->packet()->data(), |
| queued_packet->packet()->length(), |
| queued_packet->server_address().address(), |
| queued_packet->client_address()); |
| if (result.status == WRITE_STATUS_BLOCKED) { |
| // If blocked and unbuffered, return false to retry sending. |
| DCHECK(writer_->IsWriteBlocked()); |
| visitor_->OnWriteBlocked(this); |
| return writer_->IsWriteBlockedDataBuffered(); |
| } else if (result.status == WRITE_STATUS_ERROR) { |
| LOG(WARNING) << "Received unknown error while sending reset packet to " |
| << queued_packet->client_address().ToString() << ": " |
| << strerror(result.error_code); |
| } |
| return true; |
| } |
| |
| void QuicTimeWaitListManager::SetConnectionIdCleanUpAlarm() { |
| connection_id_clean_up_alarm_->Cancel(); |
| QuicTime now = helper_->GetClock()->ApproximateNow(); |
| QuicTime next_alarm_time = now; |
| if (!connection_id_map_.empty()) { |
| QuicTime oldest_connection_id = |
| connection_id_map_.begin()->second.time_added; |
| if (now.Subtract(oldest_connection_id) < kTimeWaitPeriod_) { |
| next_alarm_time = oldest_connection_id.Add(kTimeWaitPeriod_); |
| } else { |
| LOG(ERROR) << "ConnectionId lingered for longer than kTimeWaitPeriod"; |
| } |
| } else { |
| // No connection_ids added so none will expire before kTimeWaitPeriod_. |
| next_alarm_time = now.Add(kTimeWaitPeriod_); |
| } |
| |
| connection_id_clean_up_alarm_->Set(next_alarm_time); |
| } |
| |
| bool QuicTimeWaitListManager::MaybeExpireOldestConnection( |
| QuicTime expiration_time) { |
| if (connection_id_map_.empty()) { |
| return false; |
| } |
| ConnectionIdMap::iterator it = connection_id_map_.begin(); |
| QuicTime oldest_connection_id_time = it->second.time_added; |
| if (oldest_connection_id_time > expiration_time) { |
| // Too recent, don't retire. |
| return false; |
| } |
| // This connection_id has lived its age, retire it now. |
| const QuicConnectionId connection_id = it->first; |
| delete it->second.close_packet; |
| connection_id_map_.erase(it); |
| visitor_->OnConnectionRemovedFromTimeWaitList(connection_id); |
| return true; |
| } |
| |
| void QuicTimeWaitListManager::CleanUpOldConnectionIds() { |
| QuicTime now = helper_->GetClock()->ApproximateNow(); |
| QuicTime expiration = now.Subtract(kTimeWaitPeriod_); |
| if (FLAGS_quic_limit_time_wait_list_size) { |
| while (MaybeExpireOldestConnection(expiration)) { |
| } |
| } else { |
| while (!connection_id_map_.empty()) { |
| ConnectionIdMap::iterator it = connection_id_map_.begin(); |
| QuicTime oldest_connection_id = it->second.time_added; |
| if (now.Subtract(oldest_connection_id) < kTimeWaitPeriod_) { |
| break; |
| } |
| const QuicConnectionId connection_id = it->first; |
| // This connection_id has lived its age, retire it now. |
| delete it->second.close_packet; |
| connection_id_map_.erase(it); |
| visitor_->OnConnectionRemovedFromTimeWaitList(connection_id); |
| } |
| } |
| |
| SetConnectionIdCleanUpAlarm(); |
| } |
| |
| void QuicTimeWaitListManager::TrimTimeWaitListIfNeeded() { |
| if (FLAGS_quic_limit_time_wait_list_size) { |
| if (FLAGS_quic_time_wait_list_max_connections < 0) { |
| return; |
| } |
| while (num_connections() >= |
| static_cast<size_t>(FLAGS_quic_time_wait_list_max_connections)) { |
| MaybeExpireOldestConnection(QuicTime::Infinite()); |
| } |
| } |
| } |
| |
| } // namespace net |