blob: 9277913eb2d7efc2b3821d7e3e3a7620e01489e9 [file] [log] [blame]
// 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 "quiche/quic/tools/quic_client_base.h"
#include <algorithm>
#include <memory>
#include "quiche/quic/core/crypto/quic_random.h"
#include "quiche/quic/core/http/spdy_utils.h"
#include "quiche/quic/core/quic_packet_writer.h"
#include "quiche/quic/core/quic_path_validator.h"
#include "quiche/quic/core/quic_server_id.h"
#include "quiche/quic/core/quic_utils.h"
#include "quiche/quic/platform/api/quic_flags.h"
#include "quiche/quic/platform/api/quic_logging.h"
namespace quic {
// Implements the basic feature of a result delegate for path validation for
// connection migration. If the validation succeeds, migrate to the alternative
// path. Otherwise, stay on the current path.
class QuicClientSocketMigrationValidationResultDelegate
: public QuicPathValidator::ResultDelegate {
public:
QuicClientSocketMigrationValidationResultDelegate(QuicClientBase* client)
: QuicPathValidator::ResultDelegate(), client_(client) {}
// QuicPathValidator::ResultDelegate
// Overridden to start migration and takes the ownership of the writer in the
// context.
void OnPathValidationSuccess(
std::unique_ptr<QuicPathValidationContext> context,
QuicTime /*start_time*/) override {
QUIC_DLOG(INFO) << "Successfully validated path from " << *context
<< ". Migrate to it now.";
auto migration_context = std::unique_ptr<PathMigrationContext>(
static_cast<PathMigrationContext*>(context.release()));
client_->session()->MigratePath(
migration_context->self_address(), migration_context->peer_address(),
migration_context->WriterToUse(), /*owns_writer=*/false);
QUICHE_DCHECK(migration_context->WriterToUse() != nullptr);
// Hand the ownership of the alternative writer to the client.
client_->set_writer(migration_context->ReleaseWriter());
}
void OnPathValidationFailure(
std::unique_ptr<QuicPathValidationContext> context) override {
QUIC_LOG(WARNING) << "Fail to validate path " << *context
<< ", stop migrating.";
client_->session()->connection()->OnPathValidationFailureAtClient(
/*is_multi_port=*/false);
}
private:
QuicClientBase* client_;
};
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<NetworkHelper> network_helper,
std::unique_ptr<ProofVerifier> proof_verifier,
std::unique_ptr<SessionCache> session_cache)
: server_id_(server_id),
initialized_(false),
local_port_(0),
config_(config),
crypto_config_(std::move(proof_verifier), std::move(session_cache)),
helper_(helper),
alarm_factory_(alarm_factory),
supported_versions_(supported_versions),
initial_max_packet_length_(0),
num_sent_client_hellos_(0),
connection_error_(QUIC_NO_ERROR),
connected_or_attempting_connect_(false),
network_helper_(std::move(network_helper)),
connection_debug_visitor_(nullptr),
server_connection_id_length_(kQuicDefaultConnectionIdLength),
client_connection_id_length_(0) {}
QuicClientBase::~QuicClientBase() = default;
bool QuicClientBase::Initialize() {
num_sent_client_hellos_ = 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() ==
kDefaultFlowControlSendWindow) {
config()->SetInitialStreamFlowControlWindowToSend(kStreamMaxRecvWindowSize);
}
if (config()->GetInitialSessionFlowControlWindowToSend() ==
kDefaultFlowControlSendWindow) {
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.
int num_attempts = 0;
while (!connected() &&
num_attempts <= QuicCryptoClientStream::kMaxClientHellos) {
StartConnect();
while (EncryptionBeingEstablished()) {
WaitForEvents();
}
ParsedQuicVersion version = UnsupportedQuicVersion();
if (session() != nullptr && !CanReconnectWithDifferentVersion(&version)) {
// We've successfully created a session but we're not connected, and we
// cannot reconnect with a different version. Give up trying.
break;
}
num_attempts++;
}
if (session() == nullptr) {
QUIC_BUG(quic_bug_10906_1) << "Missing session after Connect";
return false;
}
return session()->connection()->connected();
}
void QuicClientBase::StartConnect() {
QUICHE_DCHECK(initialized_);
QUICHE_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()) {
// Clear queued up data if client can not try to connect with a different
// version.
if (!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();
}
const quic::ParsedQuicVersionVector client_supported_versions =
can_reconnect_with_different_version
? ParsedQuicVersionVector{mutual_version}
: supported_versions();
session_ = CreateQuicClientSession(
client_supported_versions,
new QuicConnection(GetNextConnectionId(), QuicSocketAddress(),
server_address(), helper(), alarm_factory(), writer,
/* owns_writer= */ false, Perspective::IS_CLIENT,
client_supported_versions, connection_id_generator_));
if (can_reconnect_with_different_version) {
session()->set_client_original_supported_versions(supported_versions());
}
if (connection_debug_visitor_ != nullptr) {
session()->connection()->set_debug_visitor(connection_debug_visitor_);
}
session()->connection()->set_client_connection_id(GetClientConnectionId());
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();
if (can_reconnect_with_different_version) {
// This is a reconnect using server supported |mutual_version|.
session()->connection()->SetVersionNegotiated();
}
set_connected_or_attempting_connect(true);
}
void QuicClientBase::InitializeSession() { session()->Initialize(); }
void QuicClientBase::Disconnect() {
QUICHE_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() {
if (!connected()) {
QUIC_BUG(quic_bug_10906_2)
<< "Cannot call WaitForEvents on non-connected client";
return false;
}
network_helper_->RunEventLoop();
return WaitForEventsPostprocessing();
}
bool QuicClientBase::WaitForEventsPostprocessing() {
QUICHE_DCHECK(session() != nullptr);
ParsedQuicVersion version = UnsupportedQuicVersion();
if (!connected() && CanReconnectWithDifferentVersion(&version)) {
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()) {
QUICHE_DVLOG(1)
<< "MigrateSocketWithSpecifiedPort failed as connection has closed";
return false;
}
network_helper_->CleanUpAllUDPSockets();
std::unique_ptr<QuicPacketWriter> writer =
CreateWriterForNewNetwork(new_host, port);
if (writer == nullptr) {
QUICHE_DVLOG(1)
<< "MigrateSocketWithSpecifiedPort failed from writer creation";
return false;
}
if (!session()->MigratePath(network_helper_->GetLatestClientAddress(),
session()->connection()->peer_address(),
writer.get(), false)) {
QUICHE_DVLOG(1)
<< "MigrateSocketWithSpecifiedPort failed from session()->MigratePath";
return false;
}
set_writer(writer.release());
return true;
}
bool QuicClientBase::ValidateAndMigrateSocket(const QuicIpAddress& new_host) {
QUICHE_DCHECK(VersionHasIetfQuicFrames(
session_->connection()->version().transport_version));
if (!connected()) {
return false;
}
std::unique_ptr<QuicPacketWriter> writer =
CreateWriterForNewNetwork(new_host, local_port_);
if (writer == nullptr) {
return false;
}
// Asynchronously start migration.
session_->ValidatePath(
std::make_unique<PathMigrationContext>(
std::move(writer), network_helper_->GetLatestClientAddress(),
session_->peer_address()),
std::make_unique<QuicClientSocketMigrationValidationResultDelegate>(
this));
return true;
}
std::unique_ptr<QuicPacketWriter> QuicClientBase::CreateWriterForNewNetwork(
const QuicIpAddress& new_host, int port) {
set_bind_to_address(new_host);
set_local_port(port);
if (!network_helper_->CreateUDPSocketAndBind(server_address_,
bind_to_address_, port)) {
return nullptr;
}
QuicPacketWriter* writer = network_helper_->CreateQuicPacketWriter();
QUIC_LOG_IF(WARNING, writer == writer_.get())
<< "The new writer is wrapped in the same wrapper as the old one, thus "
"appearing to have the same address as the old one.";
return std::unique_ptr<QuicPacketWriter>(writer);
}
bool QuicClientBase::ChangeEphemeralPort() {
auto current_host = network_helper_->GetLatestClientAddress().host();
return MigrateSocketWithSpecifiedPort(current_host, 0 /*any ephemeral port*/);
}
QuicSession* QuicClientBase::session() { return session_.get(); }
const QuicSession* QuicClientBase::session() const { 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) {
if (!connected()) {
QUIC_BUG(quic_bug_10906_3)
<< "Cannot WaitForStreamToClose on non-connected client";
return;
}
while (connected() && !session_->IsClosedStream(id)) {
WaitForEvents();
}
}
bool QuicClientBase::WaitForOneRttKeysAvailable() {
if (!connected()) {
QUIC_BUG(quic_bug_10906_4)
<< "Cannot WaitForOneRttKeysAvailable on non-connected client";
return false;
}
while (connected() && !session_->OneRttKeysAvailable()) {
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::WaitForHandshakeConfirmed() {
if (!session_->connection()->version().UsesTls()) {
return WaitForOneRttKeysAvailable();
}
// Otherwise, wait for receipt of HANDSHAKE_DONE frame.
while (connected() && session_->GetHandshakeState() < HANDSHAKE_CONFIRMED) {
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_->transport_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();
}
int QuicClientBase::GetNumReceivedServerConfigUpdates() {
// If we are not actively attempting to connect, the session object
// corresponds to the previous connection and should not be used.
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() {
return GenerateNewConnectionId();
}
QuicConnectionId QuicClientBase::GenerateNewConnectionId() {
return QuicUtils::CreateRandomConnectionId(server_connection_id_length_);
}
QuicConnectionId QuicClientBase::GetClientConnectionId() {
return QuicUtils::CreateRandomConnectionId(client_connection_id_length_);
}
bool QuicClientBase::CanReconnectWithDifferentVersion(
ParsedQuicVersion* version) const {
if (session_ == nullptr || session_->connection() == nullptr ||
session_->error() != QUIC_INVALID_VERSION) {
return false;
}
const auto& server_supported_versions =
session_->connection()->server_supported_versions();
if (server_supported_versions.empty()) {
return false;
}
for (const auto& client_version : supported_versions_) {
if (std::find(server_supported_versions.begin(),
server_supported_versions.end(),
client_version) != server_supported_versions.end()) {
*version = client_version;
return true;
}
}
return false;
}
bool QuicClientBase::HasPendingPathValidation() {
return session()->HasPendingPathValidation();
}
class ValidationResultDelegate : public QuicPathValidator::ResultDelegate {
public:
ValidationResultDelegate(QuicClientBase* client)
: QuicPathValidator::ResultDelegate(), client_(client) {}
void OnPathValidationSuccess(
std::unique_ptr<QuicPathValidationContext> context,
QuicTime start_time) override {
QUIC_DLOG(INFO) << "Successfully validated path from " << *context
<< ", validation started at " << start_time;
client_->AddValidatedPath(std::move(context));
}
void OnPathValidationFailure(
std::unique_ptr<QuicPathValidationContext> context) override {
QUIC_LOG(WARNING) << "Fail to validate path " << *context
<< ", stop migrating.";
client_->session()->connection()->OnPathValidationFailureAtClient(
/*is_multi_port=*/false);
}
private:
QuicClientBase* client_;
};
void QuicClientBase::ValidateNewNetwork(const QuicIpAddress& host) {
std::unique_ptr<QuicPacketWriter> writer =
CreateWriterForNewNetwork(host, local_port_);
auto result_delegate = std::make_unique<ValidationResultDelegate>(this);
if (writer == nullptr) {
result_delegate->OnPathValidationFailure(
std::make_unique<PathMigrationContext>(
nullptr, network_helper_->GetLatestClientAddress(),
session_->peer_address()));
return;
}
session()->ValidatePath(
std::make_unique<PathMigrationContext>(
std::move(writer), network_helper_->GetLatestClientAddress(),
session_->peer_address()),
std::move(result_delegate));
}
} // namespace quic