blob: 1b7ed1ed4ab747cac2af21c96f3644645ab47257 [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 "net/tools/quic/quic_client_base.h"
#include "base/strings/string_number_conversions.h"
#include "net/quic/core/crypto/quic_random.h"
#include "net/quic/core/quic_server_id.h"
#include "net/quic/core/spdy_utils.h"
using base::StringPiece;
using base::StringToInt;
using std::string;
namespace net {
void QuicClientBase::ClientQuicDataToResend::Resend() {
client_->SendRequest(*headers_, body_, fin_);
headers_ = nullptr;
}
QuicClientBase::QuicDataToResend::QuicDataToResend(
std::unique_ptr<SpdyHeaderBlock> headers,
StringPiece body,
bool fin)
: headers_(std::move(headers)), body_(body), fin_(fin) {}
QuicClientBase::QuicDataToResend::~QuicDataToResend() {}
QuicClientBase::QuicClientBase(const QuicServerId& server_id,
const QuicVersionVector& supported_versions,
const QuicConfig& config,
QuicConnectionHelperInterface* helper,
QuicAlarmFactory* alarm_factory,
std::unique_ptr<ProofVerifier> proof_verifier)
: server_id_(server_id),
initialized_(false),
local_port_(0),
config_(config),
crypto_config_(std::move(proof_verifier)),
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),
store_response_(false),
latest_response_code_(-1) {}
QuicClientBase::~QuicClientBase() {}
void QuicClientBase::OnClose(QuicSpdyStream* stream) {
DCHECK(stream != nullptr);
QuicSpdyClientStream* client_stream =
static_cast<QuicSpdyClientStream*>(stream);
const SpdyHeaderBlock& response_headers = client_stream->response_headers();
if (response_listener_ != nullptr) {
response_listener_->OnCompleteResponse(stream->id(), response_headers,
client_stream->data());
}
// Store response headers and body.
if (store_response_) {
auto status = response_headers.find(":status");
if (status == response_headers.end() ||
!StringToInt(status->second, &latest_response_code_)) {
LOG(ERROR) << "Invalid response headers";
}
latest_response_headers_ = response_headers.DebugString();
latest_response_header_block_ = response_headers.Clone();
latest_response_body_ = client_stream->data();
latest_response_trailers_ =
client_stream->received_trailers().DebugString();
}
}
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 (!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 (FLAGS_enable_quic_stateless_reject_support && connected()) {
// Resend any previously queued data.
ResendSavedData();
}
if (session() != nullptr &&
session()->error() != QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT) {
// We've successfully created a session but we're not connected, and there
// is no stateless reject to recover from. 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 = CreateQuicPacketWriter();
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.
if (session()->error() != QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT) {
ClearDataToResend();
}
// Before we destroy the last session and create a new one, gather its stats
// and update the stats for the overall connection.
UpdateStats();
}
CreateQuicClientSession(new QuicConnection(
GetNextConnectionId(), server_address(), helper(), alarm_factory(),
writer,
/* owns_writer= */ false, Perspective::IS_CLIENT, supported_versions()));
// Reset |writer()| after |session()| so that the old writer outlives the old
// session.
set_writer(writer);
session()->Initialize();
session()->CryptoConnect();
set_connected_or_attempting_connect(true);
}
void QuicClientBase::Disconnect() {
DCHECK(initialized_);
if (connected()) {
session()->connection()->CloseConnection(
QUIC_PEER_GOING_AWAY, "Client disconnecting",
ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
}
ClearDataToResend();
CleanUpAllUDPSockets();
initialized_ = false;
}
ProofVerifier* QuicClientBase::proof_verifier() const {
return crypto_config_.proof_verifier();
}
QuicClientSession* QuicClientBase::CreateQuicClientSession(
QuicConnection* connection) {
session_.reset(new QuicClientSession(config_, connection, server_id_,
&crypto_config_, &push_promise_index_));
if (initial_max_packet_length_ != 0) {
session()->connection()->SetMaxPacketLength(initial_max_packet_length_);
}
return session_.get();
}
bool QuicClientBase::EncryptionBeingEstablished() {
return !session_->IsEncryptionEstablished() &&
session_->connection()->connected();
}
void QuicClientBase::SendRequest(const SpdyHeaderBlock& headers,
StringPiece body,
bool fin) {
QuicClientPushPromiseIndex::TryHandle* handle;
QuicAsyncStatus rv = push_promise_index()->Try(headers, this, &handle);
if (rv == QUIC_SUCCESS)
return;
if (rv == QUIC_PENDING) {
// May need to retry request if asynchronous rendezvous fails.
AddPromiseDataToResend(headers, body, fin);
return;
}
QuicSpdyClientStream* stream = CreateClientStream();
if (stream == nullptr) {
QUIC_BUG << "stream creation failed!";
return;
}
stream->SendRequest(headers.Clone(), body, fin);
// Record this in case we need to resend.
MaybeAddDataToResend(headers, body, fin);
}
void QuicClientBase::SendRequestAndWaitForResponse(
const SpdyHeaderBlock& headers,
StringPiece body,
bool fin) {
SendRequest(headers, body, fin);
while (WaitForEvents()) {
}
}
void QuicClientBase::SendRequestsAndWaitForResponse(
const std::vector<string>& url_list) {
for (size_t i = 0; i < url_list.size(); ++i) {
SpdyHeaderBlock headers;
if (!SpdyUtils::PopulateHeaderBlockFromUrl(url_list[i], &headers)) {
QUIC_BUG << "Unable to create request";
continue;
}
SendRequest(headers, "", true);
}
while (WaitForEvents()) {
}
}
QuicSpdyClientStream* QuicClientBase::CreateClientStream() {
if (!connected()) {
return nullptr;
}
QuicSpdyClientStream* stream =
session_->CreateOutgoingDynamicStream(kDefaultPriority);
if (stream) {
stream->set_visitor(this);
}
return stream;
}
bool QuicClientBase::WaitForEvents() {
DCHECK(connected());
RunEventLoop();
DCHECK(session() != nullptr);
if (!connected() &&
session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT) {
DCHECK(FLAGS_enable_quic_stateless_reject_support);
DVLOG(1) << "Detected stateless reject while waiting for events. "
<< "Attempting to reconnect.";
Connect();
}
return session()->num_active_requests() != 0;
}
bool QuicClientBase::MigrateSocket(const QuicIpAddress& new_host) {
if (!connected()) {
return false;
}
CleanUpAllUDPSockets();
set_bind_to_address(new_host);
if (!CreateUDPSocketAndBind(server_address_, bind_to_address_, local_port_)) {
return false;
}
session()->connection()->SetSelfAddress(GetLatestClientAddress());
QuicPacketWriter* writer = CreateQuicPacketWriter();
set_writer(writer);
session()->connection()->SetQuicPacketWriter(writer, false);
return true;
}
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.
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
: session_->GetNumSentClientHellos();
return num_sent_client_hellos_ + current_session_hellos;
}
void QuicClientBase::UpdateStats() {
num_sent_client_hellos_ += session()->GetNumSentClientHellos();
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
: session_->GetNumReceivedServerConfigUpdates();
}
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 ? 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()
: 0;
}
QuicConnectionId QuicClientBase::GenerateNewConnectionId() {
return QuicRandom::GetInstance()->RandUint64();
}
void QuicClientBase::MaybeAddDataToResend(const SpdyHeaderBlock& headers,
StringPiece body,
bool fin) {
if (!FLAGS_enable_quic_stateless_reject_support) {
return;
}
if (session()->IsCryptoHandshakeConfirmed()) {
// The handshake is confirmed. No need to continue saving requests to
// resend.
data_to_resend_on_connect_.clear();
return;
}
// The handshake is not confirmed. Push the data onto the queue of data to
// resend if statelessly rejected.
std::unique_ptr<SpdyHeaderBlock> new_headers(
new SpdyHeaderBlock(headers.Clone()));
std::unique_ptr<QuicDataToResend> data_to_resend(
new ClientQuicDataToResend(std::move(new_headers), body, fin, this));
MaybeAddQuicDataToResend(std::move(data_to_resend));
}
void QuicClientBase::MaybeAddQuicDataToResend(
std::unique_ptr<QuicDataToResend> data_to_resend) {
data_to_resend_on_connect_.push_back(std::move(data_to_resend));
}
void QuicClientBase::ClearDataToResend() {
data_to_resend_on_connect_.clear();
}
void QuicClientBase::ResendSavedData() {
// Calling Resend will re-enqueue the data, so swap out
// data_to_resend_on_connect_ before iterating.
std::vector<std::unique_ptr<QuicDataToResend>> old_data;
old_data.swap(data_to_resend_on_connect_);
for (const auto& data : old_data) {
data->Resend();
}
}
void QuicClientBase::AddPromiseDataToResend(const SpdyHeaderBlock& headers,
StringPiece body,
bool fin) {
std::unique_ptr<SpdyHeaderBlock> new_headers(
new SpdyHeaderBlock(headers.Clone()));
push_promise_data_to_resend_.reset(
new ClientQuicDataToResend(std::move(new_headers), body, fin, this));
}
bool QuicClientBase::CheckVary(const SpdyHeaderBlock& client_request,
const SpdyHeaderBlock& promise_request,
const SpdyHeaderBlock& promise_response) {
return true;
}
void QuicClientBase::OnRendezvousResult(QuicSpdyStream* stream) {
std::unique_ptr<ClientQuicDataToResend> data_to_resend =
std::move(push_promise_data_to_resend_);
if (stream) {
stream->set_visitor(this);
stream->OnDataAvailable();
} else if (data_to_resend.get()) {
data_to_resend->Resend();
}
}
size_t QuicClientBase::latest_response_code() const {
QUIC_BUG_IF(!store_response_) << "Response not stored!";
return latest_response_code_;
}
const string& QuicClientBase::latest_response_headers() const {
QUIC_BUG_IF(!store_response_) << "Response not stored!";
return latest_response_headers_;
}
const SpdyHeaderBlock& QuicClientBase::latest_response_header_block() const {
QUIC_BUG_IF(!store_response_) << "Response not stored!";
return latest_response_header_block_;
}
const string& QuicClientBase::latest_response_body() const {
QUIC_BUG_IF(!store_response_) << "Response not stored!";
return latest_response_body_;
}
const string& QuicClientBase::latest_response_trailers() const {
QUIC_BUG_IF(!store_response_) << "Response not stored!";
return latest_response_trailers_;
}
} // namespace net