// Copyright (c) 2015 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/tools/quic_client_base.h" #include "net/third_party/quiche/src/quic/core/crypto/quic_random.h" #include "net/third_party/quiche/src/quic/core/http/spdy_utils.h" #include "net/third_party/quiche/src/quic/core/quic_server_id.h" #include "net/third_party/quiche/src/quic/core/quic_utils.h" #include "net/third_party/quiche/src/quic/core/tls_client_handshaker.h" #include "net/third_party/quiche/src/quic/platform/api/quic_flags.h" #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h" #include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h" namespace quic { QuicClientBase::NetworkHelper::~NetworkHelper() = default; QuicClientBase::QuicClientBase( const QuicServerId& server_id, const ParsedQuicVersionVector& supported_versions, const QuicConfig& config, QuicConnectionHelperInterface* helper, QuicAlarmFactory* alarm_factory, std::unique_ptr network_helper, std::unique_ptr proof_verifier) : server_id_(server_id), initialized_(false), local_port_(0), config_(config), crypto_config_(std::move(proof_verifier), TlsClientHandshaker::CreateSslCtx()), helper_(helper), alarm_factory_(alarm_factory), supported_versions_(supported_versions), initial_max_packet_length_(0), num_stateless_rejects_received_(0), num_sent_client_hellos_(0), connection_error_(QUIC_NO_ERROR), connected_or_attempting_connect_(false), network_helper_(std::move(network_helper)) {} QuicClientBase::~QuicClientBase() = default; bool QuicClientBase::Initialize() { num_sent_client_hellos_ = 0; num_stateless_rejects_received_ = 0; connection_error_ = QUIC_NO_ERROR; connected_or_attempting_connect_ = false; // If an initial flow control window has not explicitly been set, then use the // same values that Chrome uses. const uint32_t kSessionMaxRecvWindowSize = 15 * 1024 * 1024; // 15 MB const uint32_t kStreamMaxRecvWindowSize = 6 * 1024 * 1024; // 6 MB if (config()->GetInitialStreamFlowControlWindowToSend() == kMinimumFlowControlSendWindow) { config()->SetInitialStreamFlowControlWindowToSend(kStreamMaxRecvWindowSize); } if (config()->GetInitialSessionFlowControlWindowToSend() == kMinimumFlowControlSendWindow) { config()->SetInitialSessionFlowControlWindowToSend( kSessionMaxRecvWindowSize); } if (!network_helper_->CreateUDPSocketAndBind(server_address_, bind_to_address_, local_port_)) { return false; } initialized_ = true; return true; } bool QuicClientBase::Connect() { // Attempt multiple connects until the maximum number of client hellos have // been sent. while (!connected() && GetNumSentClientHellos() <= QuicCryptoClientStream::kMaxClientHellos) { StartConnect(); while (EncryptionBeingEstablished()) { WaitForEvents(); } if (GetQuicReloadableFlag(enable_quic_stateless_reject_support) && connected()) { // Resend any previously queued data. ResendSavedData(); } ParsedQuicVersion version = UnsupportedQuicVersion(); if (session() != nullptr && session()->error() != QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT && !CanReconnectWithDifferentVersion(&version)) { // We've successfully created a session but we're not connected, and there // is no stateless reject to recover from and cannot try to reconnect with // different version. Give up trying. break; } } if (!connected() && GetNumSentClientHellos() > QuicCryptoClientStream::kMaxClientHellos && session() != nullptr && session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT) { // The overall connection failed due too many stateless rejects. set_connection_error(QUIC_CRYPTO_TOO_MANY_REJECTS); } return session()->connection()->connected(); } void QuicClientBase::StartConnect() { DCHECK(initialized_); DCHECK(!connected()); QuicPacketWriter* writer = network_helper_->CreateQuicPacketWriter(); ParsedQuicVersion mutual_version = UnsupportedQuicVersion(); const bool can_reconnect_with_different_version = CanReconnectWithDifferentVersion(&mutual_version); if (connected_or_attempting_connect()) { // If the last error was not a stateless reject, then the queued up data // does not need to be resent. // Keep queued up data if client can try to connect with a different // version. if (session()->error() != QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT && !can_reconnect_with_different_version) { ClearDataToResend(); } // Before we destroy the last session and create a new one, gather its stats // and update the stats for the overall connection. UpdateStats(); } session_ = CreateQuicClientSession( supported_versions(), new QuicConnection(GetNextConnectionId(), server_address(), helper(), alarm_factory(), writer, /* owns_writer= */ false, Perspective::IS_CLIENT, can_reconnect_with_different_version ? ParsedQuicVersionVector{mutual_version} : supported_versions())); if (initial_max_packet_length_ != 0) { session()->connection()->SetMaxPacketLength(initial_max_packet_length_); } // Reset |writer()| after |session()| so that the old writer outlives the old // session. set_writer(writer); InitializeSession(); set_connected_or_attempting_connect(true); } void QuicClientBase::InitializeSession() { session()->Initialize(); } void QuicClientBase::Disconnect() { DCHECK(initialized_); initialized_ = false; if (connected()) { session()->connection()->CloseConnection( QUIC_PEER_GOING_AWAY, "Client disconnecting", ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); } ClearDataToResend(); network_helper_->CleanUpAllUDPSockets(); } ProofVerifier* QuicClientBase::proof_verifier() const { return crypto_config_.proof_verifier(); } bool QuicClientBase::EncryptionBeingEstablished() { return !session_->IsEncryptionEstablished() && session_->connection()->connected(); } bool QuicClientBase::WaitForEvents() { DCHECK(connected()); network_helper_->RunEventLoop(); DCHECK(session() != nullptr); ParsedQuicVersion version = UnsupportedQuicVersion(); if (!connected() && (session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT || CanReconnectWithDifferentVersion(&version))) { if (session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT) { DCHECK(GetQuicReloadableFlag(enable_quic_stateless_reject_support)); QUIC_DLOG(INFO) << "Detected stateless reject while waiting for events. " << "Attempting to reconnect."; } else { QUIC_DLOG(INFO) << "Can reconnect with version: " << version << ", attempting to reconnect."; } Connect(); } return HasActiveRequests(); } bool QuicClientBase::MigrateSocket(const QuicIpAddress& new_host) { return MigrateSocketWithSpecifiedPort(new_host, local_port_); } bool QuicClientBase::MigrateSocketWithSpecifiedPort( const QuicIpAddress& new_host, int port) { if (!connected()) { return false; } network_helper_->CleanUpAllUDPSockets(); set_bind_to_address(new_host); if (!network_helper_->CreateUDPSocketAndBind(server_address_, bind_to_address_, port)) { return false; } session()->connection()->SetSelfAddress( network_helper_->GetLatestClientAddress()); QuicPacketWriter* writer = network_helper_->CreateQuicPacketWriter(); set_writer(writer); session()->connection()->SetQuicPacketWriter(writer, false); return true; } bool QuicClientBase::ChangeEphemeralPort() { auto current_host = network_helper_->GetLatestClientAddress().host(); return MigrateSocketWithSpecifiedPort(current_host, 0 /*any ephemeral port*/); } QuicSession* QuicClientBase::session() { return session_.get(); } QuicClientBase::NetworkHelper* QuicClientBase::network_helper() { return network_helper_.get(); } const QuicClientBase::NetworkHelper* QuicClientBase::network_helper() const { return network_helper_.get(); } void QuicClientBase::WaitForStreamToClose(QuicStreamId id) { DCHECK(connected()); while (connected() && !session_->IsClosedStream(id)) { WaitForEvents(); } } bool QuicClientBase::WaitForCryptoHandshakeConfirmed() { DCHECK(connected()); while (connected() && !session_->IsCryptoHandshakeConfirmed()) { WaitForEvents(); } // If the handshake fails due to a timeout, the connection will be closed. QUIC_LOG_IF(ERROR, !connected()) << "Handshake with server failed."; return connected(); } bool QuicClientBase::connected() const { return session_.get() && session_->connection() && session_->connection()->connected(); } bool QuicClientBase::goaway_received() const { return session_ != nullptr && session_->goaway_received(); } int QuicClientBase::GetNumSentClientHellos() { // If we are not actively attempting to connect, the session object // corresponds to the previous connection and should not be used. const int current_session_hellos = !connected_or_attempting_connect_ ? 0 : GetNumSentClientHellosFromSession(); return num_sent_client_hellos_ + current_session_hellos; } void QuicClientBase::UpdateStats() { num_sent_client_hellos_ += GetNumSentClientHellosFromSession(); if (session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT) { ++num_stateless_rejects_received_; } } int QuicClientBase::GetNumReceivedServerConfigUpdates() { // If we are not actively attempting to connect, the session object // corresponds to the previous connection and should not be used. // We do not need to take stateless rejects into account, since we // don't expect any scup messages to be sent during a // statelessly-rejected connection. return !connected_or_attempting_connect_ ? 0 : GetNumReceivedServerConfigUpdatesFromSession(); } QuicErrorCode QuicClientBase::connection_error() const { // Return the high-level error if there was one. Otherwise, return the // connection error from the last session. if (connection_error_ != QUIC_NO_ERROR) { return connection_error_; } if (session_ == nullptr) { return QUIC_NO_ERROR; } return session_->error(); } QuicConnectionId QuicClientBase::GetNextConnectionId() { QuicConnectionId server_designated_id = GetNextServerDesignatedConnectionId(); return !server_designated_id.IsEmpty() ? server_designated_id : GenerateNewConnectionId(); } QuicConnectionId QuicClientBase::GetNextServerDesignatedConnectionId() { QuicCryptoClientConfig::CachedState* cached = crypto_config_.LookupOrCreate(server_id_); // If the cached state indicates that we should use a server-designated // connection ID, then return that connection ID. CHECK(cached != nullptr) << "QuicClientCryptoConfig::LookupOrCreate returned " << "unexpected nullptr."; return cached->has_server_designated_connection_id() ? cached->GetNextServerDesignatedConnectionId() : EmptyQuicConnectionId(); } QuicConnectionId QuicClientBase::GenerateNewConnectionId() { return QuicUtils::CreateRandomConnectionId(); } bool QuicClientBase::CanReconnectWithDifferentVersion( ParsedQuicVersion* version) const { if (session_ == nullptr || session_->connection() == nullptr || session_->error() != QUIC_INVALID_VERSION || session_->connection()->server_supported_versions().empty()) { return false; } for (const auto& client_version : supported_versions_) { if (QuicContainsValue(session_->connection()->server_supported_versions(), client_version)) { *version = client_version; return true; } } return false; } } // namespace quic