blob: 6889aad84464af35122d1526b6eeaec2fb533126 [file] [log] [blame]
// Copyright (c) 2012 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_chromium_client_session.h"
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/sparse_histogram.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/tick_clock.h"
#include "base/trace_event/memory_usage_estimator.h"
#include "base/values.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/base/network_activity_monitor.h"
#include "net/base/url_util.h"
#include "net/http/transport_security_state.h"
#include "net/log/net_log_event_type.h"
#include "net/log/net_log_source_type.h"
#include "net/quic/crypto/proof_verifier_chromium.h"
#include "net/quic/quic_chromium_connection_helper.h"
#include "net/quic/quic_chromium_packet_writer.h"
#include "net/quic/quic_connectivity_probing_manager.h"
#include "net/quic/quic_crypto_client_stream_factory.h"
#include "net/quic/quic_server_info.h"
#include "net/quic/quic_stream_factory.h"
#include "net/socket/datagram_client_socket.h"
#include "net/spdy/spdy_http_utils.h"
#include "net/spdy/spdy_log_util.h"
#include "net/spdy/spdy_session.h"
#include "net/ssl/channel_id_service.h"
#include "net/ssl/ssl_connection_status_flags.h"
#include "net/ssl/ssl_info.h"
#include "net/third_party/quic/core/http/quic_client_promised_info.h"
#include "net/third_party/quic/core/http/spdy_utils.h"
#include "net/third_party/quic/core/quic_utils.h"
#include "net/third_party/quic/platform/api/quic_flags.h"
#include "net/third_party/quic/platform/api/quic_ptr_util.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "third_party/boringssl/src/include/openssl/ssl.h"
namespace net {
namespace {
// IPv6 packets have an additional 20 bytes of overhead than IPv4 packets.
const size_t kAdditionalOverheadForIPv6 = 20;
// Maximum number of Readers that are created for any session due to
// connection migration. A new Reader is created every time this endpoint's
// IP address changes.
const size_t kMaxReadersPerQuicSession = 5;
// Time to wait (in seconds) when no networks are available and
// migrating sessions need to wait for a new network to connect.
const size_t kWaitTimeForNewNetworkSecs = 10;
const size_t kMinRetryTimeForDefaultNetworkSecs = 1;
// Maximum RTT time for this session when set initial timeout for probing
// network.
const int kDefaultRTTMilliSecs = 300;
// The maximum size of uncompressed QUIC headers that will be allowed.
const size_t kMaxUncompressedHeaderSize = 256 * 1024;
// Histograms for tracking down the crashes from http://crbug.com/354669
// Note: these values must be kept in sync with the corresponding values in:
// tools/metrics/histograms/histograms.xml
enum Location {
DESTRUCTOR = 0,
ADD_OBSERVER = 1,
TRY_CREATE_STREAM = 2,
CREATE_OUTGOING_RELIABLE_STREAM = 3,
NOTIFY_FACTORY_OF_SESSION_CLOSED_LATER = 4,
NOTIFY_FACTORY_OF_SESSION_CLOSED = 5,
NUM_LOCATIONS = 6,
};
void RecordUnexpectedOpenStreams(Location location) {
UMA_HISTOGRAM_ENUMERATION("Net.QuicSession.UnexpectedOpenStreams", location,
NUM_LOCATIONS);
}
void RecordUnexpectedObservers(Location location) {
UMA_HISTOGRAM_ENUMERATION("Net.QuicSession.UnexpectedObservers", location,
NUM_LOCATIONS);
}
void RecordUnexpectedNotGoingAway(Location location) {
UMA_HISTOGRAM_ENUMERATION("Net.QuicSession.UnexpectedNotGoingAway", location,
NUM_LOCATIONS);
}
NetLogParametersCallback NetLogQuicConnectionMigrationTriggerCallback(
const char* trigger) {
return NetLog::StringCallback("trigger", trigger);
}
std::unique_ptr<base::Value> NetLogQuicConnectionMigrationFailureCallback(
quic::QuicConnectionId connection_id,
std::string reason,
NetLogCaptureMode capture_mode) {
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
dict->SetString("connection_id", connection_id.ToString());
dict->SetString("reason", reason);
return std::move(dict);
}
std::unique_ptr<base::Value> NetLogQuicConnectionMigrationSuccessCallback(
quic::QuicConnectionId connection_id,
NetLogCaptureMode capture_mode) {
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
dict->SetString("connection_id", connection_id.ToString());
return std::move(dict);
}
std::unique_ptr<base::Value> NetLogProbingResultCallback(
NetworkChangeNotifier::NetworkHandle network,
const quic::QuicSocketAddress* peer_address,
bool is_success,
NetLogCaptureMode capture_mode) {
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
dict->SetString("network", base::NumberToString(network));
dict->SetString("peer address", peer_address->ToString());
dict->SetBoolean("is_success", is_success);
return std::move(dict);
}
// Histogram for recording the different reasons that a QUIC session is unable
// to complete the handshake.
enum HandshakeFailureReason {
HANDSHAKE_FAILURE_UNKNOWN = 0,
HANDSHAKE_FAILURE_BLACK_HOLE = 1,
HANDSHAKE_FAILURE_PUBLIC_RESET = 2,
NUM_HANDSHAKE_FAILURE_REASONS = 3,
};
void RecordHandshakeFailureReason(HandshakeFailureReason reason) {
UMA_HISTOGRAM_ENUMERATION(
"Net.QuicSession.ConnectionClose.HandshakeNotConfirmed.Reason", reason,
NUM_HANDSHAKE_FAILURE_REASONS);
}
// Note: these values must be kept in sync with the corresponding values in:
// tools/metrics/histograms/histograms.xml
enum HandshakeState {
STATE_STARTED = 0,
STATE_ENCRYPTION_ESTABLISHED = 1,
STATE_HANDSHAKE_CONFIRMED = 2,
STATE_FAILED = 3,
NUM_HANDSHAKE_STATES = 4
};
void RecordHandshakeState(HandshakeState state) {
UMA_HISTOGRAM_ENUMERATION("Net.QuicHandshakeState", state,
NUM_HANDSHAKE_STATES);
}
std::string ConnectionMigrationCauseToString(ConnectionMigrationCause cause) {
switch (cause) {
case UNKNOWN_CAUSE:
return "Unknown";
case ON_NETWORK_CONNECTED:
return "OnNetworkConnected";
case ON_NETWORK_DISCONNECTED:
return "OnNetworkDisconnected";
case ON_WRITE_ERROR:
return "OnWriteError";
case ON_NETWORK_MADE_DEFAULT:
return "OnNetworkMadeDefault";
case ON_MIGRATE_BACK_TO_DEFAULT_NETWORK:
return "OnMigrateBackToDefaultNetwork";
case ON_PATH_DEGRADING:
return "OnPathDegrading";
default:
QUIC_NOTREACHED();
break;
}
return "InvalidCause";
}
std::unique_ptr<base::Value> NetLogQuicClientSessionCallback(
const quic::QuicServerId* server_id,
int cert_verify_flags,
bool require_confirmation,
NetLogCaptureMode /* capture_mode */) {
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
dict->SetString("host", server_id->host());
dict->SetInteger("port", server_id->port());
dict->SetBoolean("privacy_mode", server_id->privacy_mode_enabled());
dict->SetBoolean("require_confirmation", require_confirmation);
dict->SetInteger("cert_verify_flags", cert_verify_flags);
return std::move(dict);
}
std::unique_ptr<base::Value> NetLogQuicPushPromiseReceivedCallback(
const spdy::SpdyHeaderBlock* headers,
spdy::SpdyStreamId stream_id,
spdy::SpdyStreamId promised_stream_id,
NetLogCaptureMode capture_mode) {
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
dict->Set("headers", ElideSpdyHeaderBlockForNetLog(*headers, capture_mode));
dict->SetInteger("id", stream_id);
dict->SetInteger("promised_stream_id", promised_stream_id);
return std::move(dict);
}
// TODO(fayang): Remove this when necessary data is collected.
void LogProbeResultToHistogram(ConnectionMigrationCause cause, bool success) {
UMA_HISTOGRAM_BOOLEAN("Net.QuicSession.ConnectionMigrationProbeSuccess",
success);
const std::string histogram_name =
"Net.QuicSession.ConnectionMigrationProbeSuccess." +
ConnectionMigrationCauseToString(cause);
STATIC_HISTOGRAM_POINTER_GROUP(
histogram_name, cause, MIGRATION_CAUSE_MAX, AddBoolean(success),
base::BooleanHistogram::FactoryGet(
histogram_name, base::HistogramBase::kUmaTargetedHistogramFlag));
}
class HpackEncoderDebugVisitor : public quic::QuicHpackDebugVisitor {
void OnUseEntry(quic::QuicTime::Delta elapsed) override {
UMA_HISTOGRAM_TIMES(
"Net.QuicHpackEncoder.IndexedEntryAge",
base::TimeDelta::FromMicroseconds(elapsed.ToMicroseconds()));
}
};
class HpackDecoderDebugVisitor : public quic::QuicHpackDebugVisitor {
void OnUseEntry(quic::QuicTime::Delta elapsed) override {
UMA_HISTOGRAM_TIMES(
"Net.QuicHpackDecoder.IndexedEntryAge",
base::TimeDelta::FromMicroseconds(elapsed.ToMicroseconds()));
}
};
class QuicServerPushHelper : public ServerPushDelegate::ServerPushHelper {
public:
explicit QuicServerPushHelper(
base::WeakPtr<QuicChromiumClientSession> session,
const GURL& url)
: session_(session), request_url_(url) {}
void Cancel() override {
if (session_) {
session_->CancelPush(request_url_);
}
}
const GURL& GetURL() const override { return request_url_; }
private:
base::WeakPtr<QuicChromiumClientSession> session_;
const GURL request_url_;
};
} // namespace
QuicChromiumClientSession::Handle::Handle(
const base::WeakPtr<QuicChromiumClientSession>& session,
const HostPortPair& destination)
: MultiplexedSessionHandle(session),
session_(session),
destination_(destination),
net_log_(session_->net_log()),
was_handshake_confirmed_(session->IsCryptoHandshakeConfirmed()),
net_error_(OK),
quic_error_(quic::QUIC_NO_ERROR),
port_migration_detected_(false),
server_id_(session_->server_id()),
quic_version_(session->connection()->transport_version()),
push_handle_(nullptr),
was_ever_used_(false) {
DCHECK(session_);
session_->AddHandle(this);
}
QuicChromiumClientSession::Handle::~Handle() {
if (push_handle_) {
auto* push_handle = push_handle_;
push_handle_ = nullptr;
push_handle->Cancel();
}
if (session_)
session_->RemoveHandle(this);
}
void QuicChromiumClientSession::Handle::OnCryptoHandshakeConfirmed() {
was_handshake_confirmed_ = true;
}
void QuicChromiumClientSession::Handle::OnSessionClosed(
quic::QuicTransportVersion quic_version,
int net_error,
quic::QuicErrorCode quic_error,
bool port_migration_detected,
LoadTimingInfo::ConnectTiming connect_timing,
bool was_ever_used) {
session_ = nullptr;
port_migration_detected_ = port_migration_detected;
net_error_ = net_error;
quic_error_ = quic_error;
quic_version_ = quic_version;
connect_timing_ = connect_timing;
push_handle_ = nullptr;
was_ever_used_ = was_ever_used;
}
bool QuicChromiumClientSession::Handle::IsConnected() const {
return session_ != nullptr;
}
bool QuicChromiumClientSession::Handle::IsCryptoHandshakeConfirmed() const {
return was_handshake_confirmed_;
}
const LoadTimingInfo::ConnectTiming&
QuicChromiumClientSession::Handle::GetConnectTiming() {
if (!session_)
return connect_timing_;
return session_->GetConnectTiming();
}
void QuicChromiumClientSession::Handle::PopulateNetErrorDetails(
NetErrorDetails* details) const {
if (session_) {
session_->PopulateNetErrorDetails(details);
} else {
details->quic_port_migration_detected = port_migration_detected_;
details->quic_connection_error = quic_error_;
}
}
quic::QuicTransportVersion QuicChromiumClientSession::Handle::GetQuicVersion()
const {
if (!session_)
return quic_version_;
return session_->connection()->transport_version();
}
void QuicChromiumClientSession::Handle::ResetPromised(
quic::QuicStreamId id,
quic::QuicRstStreamErrorCode error_code) {
if (session_)
session_->ResetPromised(id, error_code);
}
std::unique_ptr<quic::QuicConnection::ScopedPacketFlusher>
QuicChromiumClientSession::Handle::CreatePacketBundler(
quic::QuicConnection::AckBundling bundling_mode) {
if (!session_)
return nullptr;
return std::make_unique<quic::QuicConnection::ScopedPacketFlusher>(
session_->connection(), bundling_mode);
}
bool QuicChromiumClientSession::Handle::SharesSameSession(
const Handle& other) const {
return session_.get() == other.session_.get();
}
int QuicChromiumClientSession::Handle::RendezvousWithPromised(
const spdy::SpdyHeaderBlock& headers,
CompletionOnceCallback callback) {
if (!session_)
return ERR_CONNECTION_CLOSED;
quic::QuicAsyncStatus push_status =
session_->push_promise_index()->Try(headers, this, &push_handle_);
switch (push_status) {
case quic::QUIC_FAILURE:
return ERR_FAILED;
case quic::QUIC_SUCCESS:
return OK;
case quic::QUIC_PENDING:
push_callback_ = std::move(callback);
return ERR_IO_PENDING;
}
NOTREACHED();
return ERR_UNEXPECTED;
}
int QuicChromiumClientSession::Handle::RequestStream(
bool requires_confirmation,
CompletionOnceCallback callback,
const NetworkTrafficAnnotationTag& traffic_annotation) {
DCHECK(!stream_request_);
if (!session_)
return ERR_CONNECTION_CLOSED;
// std::make_unique does not work because the StreamRequest constructor
// is private.
stream_request_ = base::WrapUnique(
new StreamRequest(this, requires_confirmation, traffic_annotation));
return stream_request_->StartRequest(std::move(callback));
}
std::unique_ptr<QuicChromiumClientStream::Handle>
QuicChromiumClientSession::Handle::ReleaseStream() {
DCHECK(stream_request_);
auto handle = stream_request_->ReleaseStream();
stream_request_.reset();
return handle;
}
std::unique_ptr<QuicChromiumClientStream::Handle>
QuicChromiumClientSession::Handle::ReleasePromisedStream() {
DCHECK(push_stream_);
return std::move(push_stream_);
}
int QuicChromiumClientSession::Handle::WaitForHandshakeConfirmation(
CompletionOnceCallback callback) {
if (!session_)
return ERR_CONNECTION_CLOSED;
return session_->WaitForHandshakeConfirmation(std::move(callback));
}
void QuicChromiumClientSession::Handle::CancelRequest(StreamRequest* request) {
if (session_)
session_->CancelRequest(request);
}
int QuicChromiumClientSession::Handle::TryCreateStream(StreamRequest* request) {
if (!session_)
return ERR_CONNECTION_CLOSED;
return session_->TryCreateStream(request);
}
quic::QuicClientPushPromiseIndex*
QuicChromiumClientSession::Handle::GetPushPromiseIndex() {
if (!session_)
return push_promise_index_;
return session_->push_promise_index();
}
int QuicChromiumClientSession::Handle::GetPeerAddress(
IPEndPoint* address) const {
if (!session_)
return ERR_CONNECTION_CLOSED;
*address = session_->peer_address().impl().socket_address();
return OK;
}
int QuicChromiumClientSession::Handle::GetSelfAddress(
IPEndPoint* address) const {
if (!session_)
return ERR_CONNECTION_CLOSED;
*address = session_->self_address().impl().socket_address();
return OK;
}
bool QuicChromiumClientSession::Handle::WasEverUsed() const {
if (!session_)
return was_ever_used_;
return session_->WasConnectionEverUsed();
}
bool QuicChromiumClientSession::Handle::CheckVary(
const spdy::SpdyHeaderBlock& client_request,
const spdy::SpdyHeaderBlock& promise_request,
const spdy::SpdyHeaderBlock& promise_response) {
HttpRequestInfo promise_request_info;
ConvertHeaderBlockToHttpRequestHeaders(promise_request,
&promise_request_info.extra_headers);
HttpRequestInfo client_request_info;
ConvertHeaderBlockToHttpRequestHeaders(client_request,
&client_request_info.extra_headers);
HttpResponseInfo promise_response_info;
if (!SpdyHeadersToHttpResponse(promise_response, &promise_response_info)) {
DLOG(WARNING) << "Invalid headers";
return false;
}
HttpVaryData vary_data;
if (!vary_data.Init(promise_request_info,
*promise_response_info.headers.get())) {
// Promise didn't contain valid vary info, so URL match was sufficient.
return true;
}
// Now compare the client request for matching.
return vary_data.MatchesRequest(client_request_info,
*promise_response_info.headers.get());
}
void QuicChromiumClientSession::Handle::OnRendezvousResult(
quic::QuicSpdyStream* stream) {
DCHECK(!push_stream_);
int rv = ERR_FAILED;
if (stream) {
rv = OK;
push_stream_ =
static_cast<QuicChromiumClientStream*>(stream)->CreateHandle();
}
if (push_callback_) {
DCHECK(push_handle_);
push_handle_ = nullptr;
base::ResetAndReturn(&push_callback_).Run(rv);
}
}
QuicChromiumClientSession::StreamRequest::StreamRequest(
QuicChromiumClientSession::Handle* session,
bool requires_confirmation,
const NetworkTrafficAnnotationTag& traffic_annotation)
: session_(session),
requires_confirmation_(requires_confirmation),
stream_(nullptr),
traffic_annotation_(traffic_annotation),
weak_factory_(this) {}
QuicChromiumClientSession::StreamRequest::~StreamRequest() {
if (stream_)
stream_->Reset(quic::QUIC_STREAM_CANCELLED);
if (session_)
session_->CancelRequest(this);
}
int QuicChromiumClientSession::StreamRequest::StartRequest(
CompletionOnceCallback callback) {
if (!session_->IsConnected())
return ERR_CONNECTION_CLOSED;
next_state_ = STATE_WAIT_FOR_CONFIRMATION;
int rv = DoLoop(OK);
if (rv == ERR_IO_PENDING)
callback_ = std::move(callback);
return rv;
}
std::unique_ptr<QuicChromiumClientStream::Handle>
QuicChromiumClientSession::StreamRequest::ReleaseStream() {
DCHECK(stream_);
return std::move(stream_);
}
void QuicChromiumClientSession::StreamRequest::OnRequestCompleteSuccess(
std::unique_ptr<QuicChromiumClientStream::Handle> stream) {
DCHECK_EQ(STATE_REQUEST_STREAM_COMPLETE, next_state_);
stream_ = std::move(stream);
// This method is called even when the request completes synchronously.
if (callback_)
DoCallback(OK);
}
void QuicChromiumClientSession::StreamRequest::OnRequestCompleteFailure(
int rv) {
DCHECK_EQ(STATE_REQUEST_STREAM_COMPLETE, next_state_);
// This method is called even when the request completes synchronously.
if (callback_) {
// Avoid re-entrancy if the callback calls into the session.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&QuicChromiumClientSession::StreamRequest::DoCallback,
weak_factory_.GetWeakPtr(), rv));
}
}
void QuicChromiumClientSession::StreamRequest::OnIOComplete(int rv) {
rv = DoLoop(rv);
if (rv != ERR_IO_PENDING && !callback_.is_null()) {
DoCallback(rv);
}
}
void QuicChromiumClientSession::StreamRequest::DoCallback(int rv) {
CHECK_NE(rv, ERR_IO_PENDING);
CHECK(!callback_.is_null());
// The client callback can do anything, including destroying this class,
// so any pending callback must be issued after everything else is done.
base::ResetAndReturn(&callback_).Run(rv);
}
int QuicChromiumClientSession::StreamRequest::DoLoop(int rv) {
do {
State state = next_state_;
next_state_ = STATE_NONE;
switch (state) {
case STATE_WAIT_FOR_CONFIRMATION:
CHECK_EQ(OK, rv);
rv = DoWaitForConfirmation();
break;
case STATE_WAIT_FOR_CONFIRMATION_COMPLETE:
rv = DoWaitForConfirmationComplete(rv);
break;
case STATE_REQUEST_STREAM:
CHECK_EQ(OK, rv);
rv = DoRequestStream();
break;
case STATE_REQUEST_STREAM_COMPLETE:
rv = DoRequestStreamComplete(rv);
break;
default:
NOTREACHED() << "next_state_: " << next_state_;
break;
}
} while (next_state_ != STATE_NONE && next_state_ && rv != ERR_IO_PENDING);
return rv;
}
int QuicChromiumClientSession::StreamRequest::DoWaitForConfirmation() {
next_state_ = STATE_WAIT_FOR_CONFIRMATION_COMPLETE;
if (requires_confirmation_) {
return session_->WaitForHandshakeConfirmation(
base::Bind(&QuicChromiumClientSession::StreamRequest::OnIOComplete,
weak_factory_.GetWeakPtr()));
}
return OK;
}
int QuicChromiumClientSession::StreamRequest::DoWaitForConfirmationComplete(
int rv) {
DCHECK_NE(ERR_IO_PENDING, rv);
if (rv < 0)
return rv;
next_state_ = STATE_REQUEST_STREAM;
return OK;
}
int QuicChromiumClientSession::StreamRequest::DoRequestStream() {
next_state_ = STATE_REQUEST_STREAM_COMPLETE;
return session_->TryCreateStream(this);
}
int QuicChromiumClientSession::StreamRequest::DoRequestStreamComplete(int rv) {
DCHECK(rv == OK || !stream_);
return rv;
}
QuicChromiumClientSession::QuicChromiumClientSession(
quic::QuicConnection* connection,
std::unique_ptr<DatagramClientSocket> socket,
QuicStreamFactory* stream_factory,
QuicCryptoClientStreamFactory* crypto_client_stream_factory,
quic::QuicClock* clock,
TransportSecurityState* transport_security_state,
SSLConfigService* ssl_config_service,
std::unique_ptr<QuicServerInfo> server_info,
const QuicSessionKey& session_key,
bool require_confirmation,
bool migrate_session_early_v2,
bool migrate_sessions_on_network_change_v2,
NetworkChangeNotifier::NetworkHandle default_network,
quic::QuicTime::Delta retransmittable_on_wire_timeout,
bool migrate_idle_session,
base::TimeDelta idle_migration_period,
base::TimeDelta max_time_on_non_default_network,
int max_migrations_to_non_default_network_on_write_error,
int max_migrations_to_non_default_network_on_path_degrading,
int yield_after_packets,
quic::QuicTime::Delta yield_after_duration,
bool go_away_on_path_degrading,
bool headers_include_h2_stream_dependency,
int cert_verify_flags,
const quic::QuicConfig& config,
quic::QuicCryptoClientConfig* crypto_config,
const char* const connection_description,
base::TimeTicks dns_resolution_start_time,
base::TimeTicks dns_resolution_end_time,
quic::QuicClientPushPromiseIndex* push_promise_index,
ServerPushDelegate* push_delegate,
const base::TickClock* tick_clock,
base::SequencedTaskRunner* task_runner,
std::unique_ptr<SocketPerformanceWatcher> socket_performance_watcher,
NetLog* net_log)
: quic::QuicSpdyClientSessionBase(connection,
push_promise_index,
config,
connection->supported_versions()),
session_key_(session_key),
require_confirmation_(require_confirmation),
migrate_session_early_v2_(migrate_session_early_v2),
migrate_session_on_network_change_v2_(
migrate_sessions_on_network_change_v2),
migrate_idle_session_(migrate_idle_session),
idle_migration_period_(idle_migration_period),
max_time_on_non_default_network_(max_time_on_non_default_network),
max_migrations_to_non_default_network_on_write_error_(
max_migrations_to_non_default_network_on_write_error),
current_migrations_to_non_default_network_on_write_error_(0),
max_migrations_to_non_default_network_on_path_degrading_(
max_migrations_to_non_default_network_on_path_degrading),
current_migrations_to_non_default_network_on_path_degrading_(0),
clock_(clock),
yield_after_packets_(yield_after_packets),
yield_after_duration_(yield_after_duration),
go_away_on_path_degrading_(go_away_on_path_degrading),
most_recent_path_degrading_timestamp_(base::TimeTicks()),
most_recent_network_disconnected_timestamp_(base::TimeTicks()),
tick_clock_(tick_clock),
most_recent_stream_close_time_(tick_clock_->NowTicks()),
most_recent_write_error_(0),
most_recent_write_error_timestamp_(base::TimeTicks()),
stream_factory_(stream_factory),
transport_security_state_(transport_security_state),
ssl_config_service_(ssl_config_service),
server_info_(std::move(server_info)),
pkp_bypassed_(false),
is_fatal_cert_error_(false),
num_total_streams_(0),
task_runner_(task_runner),
net_log_(NetLogWithSource::Make(net_log, NetLogSourceType::QUIC_SESSION)),
logger_(new QuicConnectionLogger(this,
connection_description,
std::move(socket_performance_watcher),
net_log_)),
going_away_(false),
port_migration_detected_(false),
push_delegate_(push_delegate),
streams_pushed_count_(0),
streams_pushed_and_claimed_count_(0),
bytes_pushed_count_(0),
bytes_pushed_and_unclaimed_count_(0),
probing_manager_(this, task_runner_),
retry_migrate_back_count_(0),
current_connection_migration_cause_(UNKNOWN_CAUSE),
send_packet_after_migration_(false),
wait_for_new_network_(false),
ignore_read_error_(false),
headers_include_h2_stream_dependency_(
headers_include_h2_stream_dependency &&
this->connection()->transport_version() >= quic::QUIC_VERSION_43),
weak_factory_(this) {
// Make sure connection migration and goaway on path degrading are not turned
// on at the same time.
DCHECK(!(migrate_session_early_v2_ && go_away_on_path_degrading_));
default_network_ = default_network;
auto* socket_raw = socket.get();
sockets_.push_back(std::move(socket));
packet_readers_.push_back(std::make_unique<QuicChromiumPacketReader>(
sockets_.back().get(), clock, this, yield_after_packets,
yield_after_duration, net_log_));
crypto_stream_.reset(
crypto_client_stream_factory->CreateQuicCryptoClientStream(
session_key.server_id(), this,
std::make_unique<ProofVerifyContextChromium>(cert_verify_flags,
net_log_),
crypto_config));
connection->set_debug_visitor(logger_.get());
connection->set_creator_debug_delegate(logger_.get());
migrate_back_to_default_timer_.SetTaskRunner(task_runner_);
net_log_.BeginEvent(
NetLogEventType::QUIC_SESSION,
base::Bind(NetLogQuicClientSessionCallback, &session_key.server_id(),
cert_verify_flags, require_confirmation_));
IPEndPoint address;
if (socket_raw && socket_raw->GetLocalAddress(&address) == OK &&
address.GetFamily() == ADDRESS_FAMILY_IPV6) {
connection->SetMaxPacketLength(connection->max_packet_length() -
kAdditionalOverheadForIPv6);
}
connect_timing_.dns_start = dns_resolution_start_time;
connect_timing_.dns_end = dns_resolution_end_time;
if (migrate_session_early_v2_) {
connection->set_retransmittable_on_wire_timeout(
retransmittable_on_wire_timeout);
}
}
QuicChromiumClientSession::~QuicChromiumClientSession() {
DCHECK(callback_.is_null());
net_log_.EndEvent(NetLogEventType::QUIC_SESSION);
DCHECK(waiting_for_confirmation_callbacks_.empty());
if (!dynamic_streams().empty())
RecordUnexpectedOpenStreams(DESTRUCTOR);
if (!handles_.empty())
RecordUnexpectedObservers(DESTRUCTOR);
if (!going_away_)
RecordUnexpectedNotGoingAway(DESTRUCTOR);
while (!dynamic_streams().empty() || !handles_.empty() ||
!stream_requests_.empty()) {
// The session must be closed before it is destroyed.
DCHECK(dynamic_streams().empty());
CloseAllStreams(ERR_UNEXPECTED);
DCHECK(handles_.empty());
CloseAllHandles(ERR_UNEXPECTED);
CancelAllRequests(ERR_UNEXPECTED);
connection()->set_debug_visitor(nullptr);
}
if (connection()->connected()) {
// Ensure that the connection is closed by the time the session is
// destroyed.
connection()->CloseConnection(quic::QUIC_INTERNAL_ERROR,
"session torn down",
quic::ConnectionCloseBehavior::SILENT_CLOSE);
}
if (IsEncryptionEstablished())
RecordHandshakeState(STATE_ENCRYPTION_ESTABLISHED);
if (IsCryptoHandshakeConfirmed())
RecordHandshakeState(STATE_HANDSHAKE_CONFIRMED);
else
RecordHandshakeState(STATE_FAILED);
UMA_HISTOGRAM_COUNTS_1M("Net.QuicSession.NumTotalStreams",
num_total_streams_);
UMA_HISTOGRAM_COUNTS_1M("Net.QuicNumSentClientHellos",
crypto_stream_->num_sent_client_hellos());
UMA_HISTOGRAM_COUNTS_1M("Net.QuicSession.Pushed", streams_pushed_count_);
UMA_HISTOGRAM_COUNTS_1M("Net.QuicSession.PushedAndClaimed",
streams_pushed_and_claimed_count_);
UMA_HISTOGRAM_COUNTS_1M("Net.QuicSession.PushedBytes", bytes_pushed_count_);
DCHECK_LE(bytes_pushed_and_unclaimed_count_, bytes_pushed_count_);
UMA_HISTOGRAM_COUNTS_1M("Net.QuicSession.PushedAndUnclaimedBytes",
bytes_pushed_and_unclaimed_count_);
if (!IsCryptoHandshakeConfirmed())
return;
// Sending one client_hello means we had zero handshake-round-trips.
int round_trip_handshakes = crypto_stream_->num_sent_client_hellos() - 1;
// Don't bother with these histogram during tests, which mock out
// num_sent_client_hellos().
if (round_trip_handshakes < 0 || !stream_factory_)
return;
SSLInfo ssl_info;
// QUIC supports only secure urls.
if (GetSSLInfo(&ssl_info) && ssl_info.cert.get()) {
UMA_HISTOGRAM_CUSTOM_COUNTS("Net.QuicSession.ConnectRandomPortForHTTPS",
round_trip_handshakes, 1, 3, 4);
if (require_confirmation_) {
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Net.QuicSession.ConnectRandomPortRequiringConfirmationForHTTPS",
round_trip_handshakes, 1, 3, 4);
}
}
const quic::QuicConnectionStats stats = connection()->GetStats();
// The MTU used by QUIC is limited to a fairly small set of predefined values
// (initial values and MTU discovery values), but does not fare well when
// bucketed. Because of that, a sparse histogram is used here.
base::UmaHistogramSparse("Net.QuicSession.ClientSideMtu",
connection()->max_packet_length());
base::UmaHistogramSparse("Net.QuicSession.ServerSideMtu",
stats.max_received_packet_size);
UMA_HISTOGRAM_COUNTS_1M("Net.QuicSession.MtuProbesSent",
connection()->mtu_probe_count());
if (stats.packets_sent >= 100) {
// Used to monitor for regressions that effect large uploads.
UMA_HISTOGRAM_COUNTS_1000(
"Net.QuicSession.PacketRetransmitsPerMille",
1000 * stats.packets_retransmitted / stats.packets_sent);
}
if (stats.max_sequence_reordering == 0)
return;
const base::HistogramBase::Sample kMaxReordering = 100;
base::HistogramBase::Sample reordering = kMaxReordering;
if (stats.min_rtt_us > 0) {
reordering = static_cast<base::HistogramBase::Sample>(
100 * stats.max_time_reordering_us / stats.min_rtt_us);
}
UMA_HISTOGRAM_CUSTOM_COUNTS("Net.QuicSession.MaxReorderingTime", reordering,
1, kMaxReordering, 50);
if (stats.min_rtt_us > 100 * 1000) {
UMA_HISTOGRAM_CUSTOM_COUNTS("Net.QuicSession.MaxReorderingTimeLongRtt",
reordering, 1, kMaxReordering, 50);
}
UMA_HISTOGRAM_COUNTS_1M(
"Net.QuicSession.MaxReordering",
static_cast<base::HistogramBase::Sample>(stats.max_sequence_reordering));
}
void QuicChromiumClientSession::Initialize() {
quic::QuicSpdyClientSessionBase::Initialize();
SetHpackEncoderDebugVisitor(std::make_unique<HpackEncoderDebugVisitor>());
SetHpackDecoderDebugVisitor(std::make_unique<HpackDecoderDebugVisitor>());
set_max_uncompressed_header_bytes(kMaxUncompressedHeaderSize);
}
size_t QuicChromiumClientSession::WriteHeadersOnHeadersStream(
quic::QuicStreamId id,
spdy::SpdyHeaderBlock headers,
bool fin,
spdy::SpdyPriority priority,
quic::QuicReferenceCountedPointer<quic::QuicAckListenerInterface>
ack_listener) {
spdy::SpdyStreamId parent_stream_id = 0;
int weight = 0;
bool exclusive = false;
if (headers_include_h2_stream_dependency_) {
priority_dependency_state_.OnStreamCreation(id, priority, &parent_stream_id,
&weight, &exclusive);
} else {
weight = spdy::Spdy3PriorityToHttp2Weight(priority);
}
return WriteHeadersOnHeadersStreamImpl(id, std::move(headers), fin,
parent_stream_id, weight, exclusive,
std::move(ack_listener));
}
void QuicChromiumClientSession::UnregisterStreamPriority(quic::QuicStreamId id,
bool is_static) {
if (headers_include_h2_stream_dependency_ && !is_static) {
priority_dependency_state_.OnStreamDestruction(id);
}
quic::QuicSpdySession::UnregisterStreamPriority(id, is_static);
}
void QuicChromiumClientSession::UpdateStreamPriority(
quic::QuicStreamId id,
spdy::SpdyPriority new_priority) {
if (headers_include_h2_stream_dependency_) {
auto updates = priority_dependency_state_.OnStreamUpdate(id, new_priority);
for (auto update : updates) {
WritePriority(update.id, update.parent_stream_id, update.weight,
update.exclusive);
}
}
quic::QuicSpdySession::UpdateStreamPriority(id, new_priority);
}
void QuicChromiumClientSession::OnStreamFrame(
const quic::QuicStreamFrame& frame) {
// Record total number of stream frames.
UMA_HISTOGRAM_COUNTS_1M("Net.QuicNumStreamFramesInPacket", 1);
// Record number of frames per stream in packet.
UMA_HISTOGRAM_COUNTS_1M("Net.QuicNumStreamFramesPerStreamInPacket", 1);
return quic::QuicSpdySession::OnStreamFrame(frame);
}
void QuicChromiumClientSession::AddHandle(Handle* handle) {
if (going_away_) {
RecordUnexpectedObservers(ADD_OBSERVER);
handle->OnSessionClosed(connection()->transport_version(), ERR_UNEXPECTED,
error(), port_migration_detected_,
GetConnectTiming(), WasConnectionEverUsed());
return;
}
DCHECK(!base::ContainsKey(handles_, handle));
handles_.insert(handle);
}
void QuicChromiumClientSession::RemoveHandle(Handle* handle) {
DCHECK(base::ContainsKey(handles_, handle));
handles_.erase(handle);
}
// TODO(zhongyi): replace migration_session_* booleans with
// ConnectionMigrationMode.
ConnectionMigrationMode QuicChromiumClientSession::connection_migration_mode()
const {
if (migrate_session_early_v2_)
return ConnectionMigrationMode::FULL_MIGRATION_V2;
if (migrate_session_on_network_change_v2_)
return ConnectionMigrationMode::NO_MIGRATION_ON_PATH_DEGRADING_V2;
return ConnectionMigrationMode::NO_MIGRATION;
}
int QuicChromiumClientSession::WaitForHandshakeConfirmation(
CompletionOnceCallback callback) {
if (!connection()->connected())
return ERR_CONNECTION_CLOSED;
if (IsCryptoHandshakeConfirmed())
return OK;
waiting_for_confirmation_callbacks_.push_back(std::move(callback));
return ERR_IO_PENDING;
}
int QuicChromiumClientSession::TryCreateStream(StreamRequest* request) {
if (goaway_received()) {
DVLOG(1) << "Going away.";
return ERR_CONNECTION_CLOSED;
}
if (!connection()->connected()) {
DVLOG(1) << "Already closed.";
return ERR_CONNECTION_CLOSED;
}
if (going_away_) {
RecordUnexpectedOpenStreams(TRY_CREATE_STREAM);
return ERR_CONNECTION_CLOSED;
}
bool can_open_next;
if (!GetQuicReloadableFlag(quic_use_common_stream_check) &&
connection()->transport_version() != quic::QUIC_VERSION_99) {
can_open_next = (GetNumOpenOutgoingStreams() <
stream_id_manager().max_open_outgoing_streams());
} else {
can_open_next = CanOpenNextOutgoingBidirectionalStream();
}
if (can_open_next) {
request->stream_ =
CreateOutgoingReliableStreamImpl(request->traffic_annotation())
->CreateHandle();
return OK;
}
request->pending_start_time_ = tick_clock_->NowTicks();
stream_requests_.push_back(request);
UMA_HISTOGRAM_COUNTS_1000("Net.QuicSession.NumPendingStreamRequests",
stream_requests_.size());
return ERR_IO_PENDING;
}
void QuicChromiumClientSession::CancelRequest(StreamRequest* request) {
// Remove |request| from the queue while preserving the order of the
// other elements.
auto it =
std::find(stream_requests_.begin(), stream_requests_.end(), request);
if (it != stream_requests_.end()) {
it = stream_requests_.erase(it);
}
}
bool QuicChromiumClientSession::ShouldCreateOutgoingBidirectionalStream() {
if (!crypto_stream_->encryption_established()) {
DVLOG(1) << "Encryption not active so no outgoing stream created.";
return false;
}
if (!GetQuicReloadableFlag(quic_use_common_stream_check) &&
connection()->transport_version() != quic::QUIC_VERSION_99) {
if (GetNumOpenOutgoingStreams() >=
stream_id_manager().max_open_outgoing_streams()) {
DVLOG(1) << "Failed to create a new outgoing stream. "
<< "Already " << GetNumOpenOutgoingStreams() << " open.";
return false;
}
} else {
if (!CanOpenNextOutgoingBidirectionalStream()) {
DVLOG(1) << "Failed to create a new outgoing stream. "
<< "Already " << GetNumOpenOutgoingStreams() << " open.";
return false;
}
}
if (goaway_received()) {
DVLOG(1) << "Failed to create a new outgoing stream. "
<< "Already received goaway.";
return false;
}
if (going_away_) {
RecordUnexpectedOpenStreams(CREATE_OUTGOING_RELIABLE_STREAM);
return false;
}
return true;
}
bool QuicChromiumClientSession::ShouldCreateOutgoingUnidirectionalStream() {
NOTREACHED() << "Try to create outgoing unidirectional streams";
return false;
}
bool QuicChromiumClientSession::WasConnectionEverUsed() {
const quic::QuicConnectionStats& stats = connection()->GetStats();
return stats.bytes_sent > 0 || stats.bytes_received > 0;
}
QuicChromiumClientStream*
QuicChromiumClientSession::CreateOutgoingBidirectionalStream() {
NOTREACHED() << "CreateOutgoingReliableStreamImpl should be called directly";
return nullptr;
}
QuicChromiumClientStream*
QuicChromiumClientSession::CreateOutgoingUnidirectionalStream() {
NOTREACHED() << "Try to create outgoing unidirectional stream";
return nullptr;
}
QuicChromiumClientStream*
QuicChromiumClientSession::CreateOutgoingReliableStreamImpl(
const NetworkTrafficAnnotationTag& traffic_annotation) {
DCHECK(connection()->connected());
QuicChromiumClientStream* stream = new QuicChromiumClientStream(
GetNextOutgoingBidirectionalStreamId(), this, quic::BIDIRECTIONAL,
net_log_, traffic_annotation);
ActivateStream(base::WrapUnique(stream));
++num_total_streams_;
UMA_HISTOGRAM_COUNTS_1M("Net.QuicSession.NumOpenStreams",
GetNumOpenOutgoingStreams());
// The previous histogram puts 100 in a bucket betweeen 86-113 which does
// not shed light on if chrome ever things it has more than 100 streams open.
UMA_HISTOGRAM_BOOLEAN("Net.QuicSession.TooManyOpenStreams",
GetNumOpenOutgoingStreams() > 100);
return stream;
}
quic::QuicCryptoClientStream*
QuicChromiumClientSession::GetMutableCryptoStream() {
return crypto_stream_.get();
}
const quic::QuicCryptoClientStream* QuicChromiumClientSession::GetCryptoStream()
const {
return crypto_stream_.get();
}
bool QuicChromiumClientSession::GetRemoteEndpoint(IPEndPoint* endpoint) {
*endpoint = peer_address().impl().socket_address();
return true;
}
// TODO(rtenneti): Add unittests for GetSSLInfo which exercise the various ways
// we learn about SSL info (sync vs async vs cached).
bool QuicChromiumClientSession::GetSSLInfo(SSLInfo* ssl_info) const {
ssl_info->Reset();
if (!cert_verify_result_) {
return false;
}
ssl_info->cert_status = cert_verify_result_->cert_status;
ssl_info->cert = cert_verify_result_->verified_cert;
// Map QUIC AEADs to the corresponding TLS 1.3 cipher. OpenSSL's cipher suite
// numbers begin with a stray 0x03, so mask them off.
quic::QuicTag aead = crypto_stream_->crypto_negotiated_params().aead;
uint16_t cipher_suite;
switch (aead) {
case quic::kAESG:
cipher_suite = TLS1_CK_AES_128_GCM_SHA256 & 0xffff;
break;
case quic::kCC20:
cipher_suite = TLS1_CK_CHACHA20_POLY1305_SHA256 & 0xffff;
break;
default:
NOTREACHED();
return false;
}
int ssl_connection_status = 0;
SSLConnectionStatusSetCipherSuite(cipher_suite, &ssl_connection_status);
SSLConnectionStatusSetVersion(SSL_CONNECTION_VERSION_QUIC,
&ssl_connection_status);
// Report the QUIC key exchange as the corresponding TLS curve.
switch (crypto_stream_->crypto_negotiated_params().key_exchange) {
case quic::kP256:
ssl_info->key_exchange_group = SSL_CURVE_SECP256R1;
break;
case quic::kC255:
ssl_info->key_exchange_group = SSL_CURVE_X25519;
break;
default:
NOTREACHED();
return false;
}
// QUIC-Crypto always uses RSA-PSS or ECDSA with SHA-256.
//
// TODO(nharper): This will no longer be true in TLS 1.3. This logic, and
// likely the rest of this logic, will want some adjustments for QUIC with TLS
// 1.3.
size_t unused;
X509Certificate::PublicKeyType key_type;
X509Certificate::GetPublicKeyInfo(ssl_info->cert->cert_buffer(), &unused,
&key_type);
switch (key_type) {
case X509Certificate::kPublicKeyTypeRSA:
ssl_info->peer_signature_algorithm = SSL_SIGN_RSA_PSS_RSAE_SHA256;
break;
case X509Certificate::kPublicKeyTypeECDSA:
ssl_info->peer_signature_algorithm = SSL_SIGN_ECDSA_SECP256R1_SHA256;
break;
default:
NOTREACHED();
return false;
}
ssl_info->public_key_hashes = cert_verify_result_->public_key_hashes;
ssl_info->is_issued_by_known_root =
cert_verify_result_->is_issued_by_known_root;
ssl_info->pkp_bypassed = pkp_bypassed_;
ssl_info->connection_status = ssl_connection_status;
ssl_info->client_cert_sent = false;
ssl_info->channel_id_sent = crypto_stream_->WasChannelIDSent();
ssl_info->handshake_type = SSLInfo::HANDSHAKE_FULL;
ssl_info->pinning_failure_log = pinning_failure_log_;
ssl_info->is_fatal_cert_error = is_fatal_cert_error_;
ssl_info->UpdateCertificateTransparencyInfo(*ct_verify_result_);
return true;
}
int QuicChromiumClientSession::CryptoConnect(CompletionOnceCallback callback) {
connect_timing_.connect_start = tick_clock_->NowTicks();
RecordHandshakeState(STATE_STARTED);
DCHECK(flow_controller());
if (!crypto_stream_->CryptoConnect())
return ERR_QUIC_HANDSHAKE_FAILED;
if (IsCryptoHandshakeConfirmed()) {
connect_timing_.connect_end = tick_clock_->NowTicks();
return OK;
}
// Unless we require handshake confirmation, activate the session if
// we have established initial encryption.
if (!require_confirmation_ && IsEncryptionEstablished())
return OK;
callback_ = std::move(callback);
return ERR_IO_PENDING;
}
int QuicChromiumClientSession::GetNumSentClientHellos() const {
return crypto_stream_->num_sent_client_hellos();
}
bool QuicChromiumClientSession::CanPool(const std::string& hostname,
PrivacyMode privacy_mode,
const SocketTag& socket_tag) const {
DCHECK(connection()->connected());
if (privacy_mode != session_key_.privacy_mode() ||
socket_tag != session_key_.socket_tag()) {
// Privacy mode and socket tag must always match.
return false;
}
SSLInfo ssl_info;
if (!GetSSLInfo(&ssl_info) || !ssl_info.cert.get()) {
NOTREACHED() << "QUIC should always have certificates.";
return false;
}
return SpdySession::CanPool(transport_security_state_, ssl_info,
*ssl_config_service_, session_key_.host(),
hostname);
}
bool QuicChromiumClientSession::ShouldCreateIncomingStream(
quic::QuicStreamId id) {
if (!connection()->connected()) {
LOG(DFATAL) << "ShouldCreateIncomingStream called when disconnected";
return false;
}
if (goaway_received()) {
DVLOG(1) << "Cannot create a new outgoing stream. "
<< "Already received goaway.";
return false;
}
if (going_away_) {
return false;
}
if (quic::QuicUtils::IsClientInitiatedStreamId(
connection()->transport_version(), id) ||
(connection()->transport_version() == quic::QUIC_VERSION_99 &&
quic::QuicUtils::IsBidirectionalStreamId(id))) {
LOG(WARNING) << "Received invalid push stream id " << id;
connection()->CloseConnection(
quic::QUIC_INVALID_STREAM_ID,
"Server created non write unidirectional stream",
quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
return false;
}
return true;
}
QuicChromiumClientStream* QuicChromiumClientSession::CreateIncomingStream(
quic::QuicStreamId id) {
if (!ShouldCreateIncomingStream(id)) {
return nullptr;
}
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("quic_chromium_incoming_session", R"(
semantics {
sender: "Quic Chromium Client Session"
description:
"When a web server needs to push a response to a client, an incoming "
"stream is created to reply the client with pushed message instead "
"of a message from the network."
trigger:
"A request by a server to push a response to the client."
data: "None."
destination: OTHER
destination_other:
"This stream is not used for sending data."
}
policy {
cookies_allowed: NO
setting: "This feature cannot be disabled in settings."
policy_exception_justification:
"Essential for network access."
}
)");
return CreateIncomingReliableStreamImpl(id, traffic_annotation);
}
QuicChromiumClientStream* QuicChromiumClientSession::CreateIncomingStream(
quic::PendingStream pending) {
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation(
"quic_chromium_incoming_pending_session", R"(
semantics {
sender: "Quic Chromium Client Session Pending Stream"
description:
"When a web server needs to push a response to a client, an incoming "
"stream is created to reply to the client with pushed message instead "
"of a message from the network."
trigger:
"A request by a server to push a response to the client."
data: "This stream is only used to receive data from the server."
destination: OTHER
destination_other:
"The web server pushing the response."
}
policy {
cookies_allowed: NO
setting: "This feature cannot be disabled in settings."
policy_exception_justification:
"Essential for network access."
}
)");
return CreateIncomingReliableStreamImpl(std::move(pending),
traffic_annotation);
}
QuicChromiumClientStream*
QuicChromiumClientSession::CreateIncomingReliableStreamImpl(
quic::QuicStreamId id,
const NetworkTrafficAnnotationTag& traffic_annotation) {
DCHECK(connection()->connected());
QuicChromiumClientStream* stream = new QuicChromiumClientStream(
id, this, quic::READ_UNIDIRECTIONAL, net_log_, traffic_annotation);
ActivateStream(base::WrapUnique(stream));
++num_total_streams_;
return stream;
}
QuicChromiumClientStream*
QuicChromiumClientSession::CreateIncomingReliableStreamImpl(
quic::PendingStream pending,
const NetworkTrafficAnnotationTag& traffic_annotation) {
DCHECK(connection()->connected());
QuicChromiumClientStream* stream = new QuicChromiumClientStream(
std::move(pending), this, quic::READ_UNIDIRECTIONAL, net_log_,
traffic_annotation);
ActivateStream(base::WrapUnique(stream));
++num_total_streams_;
return stream;
}
void QuicChromiumClientSession::CloseStream(quic::QuicStreamId stream_id) {
most_recent_stream_close_time_ = tick_clock_->NowTicks();
quic::QuicStream* stream = GetOrCreateStream(stream_id);
if (stream) {
logger_->UpdateReceivedFrameCounts(stream_id, stream->num_frames_received(),
stream->num_duplicate_frames_received());
if (quic::QuicUtils::IsServerInitiatedStreamId(
connection()->transport_version(), stream_id)) {
bytes_pushed_count_ += stream->stream_bytes_read();
}
}
quic::QuicSpdySession::CloseStream(stream_id);
}
void QuicChromiumClientSession::SendRstStream(
quic::QuicStreamId id,
quic::QuicRstStreamErrorCode error,
quic::QuicStreamOffset bytes_written) {
if (quic::QuicUtils::IsServerInitiatedStreamId(
connection()->transport_version(), id)) {
StreamHandler handler = GetOrCreateStreamImpl(id, /*may_buffer=*/true);
if (handler.is_pending) {
bytes_pushed_count_ += handler.pending->stream_bytes_read();
} else if (handler.stream) {
bytes_pushed_count_ += handler.stream->stream_bytes_read();
}
}
quic::QuicSpdySession::SendRstStream(id, error, bytes_written);
}
void QuicChromiumClientSession::OnCanCreateNewOutgoingStream() {
if (CanOpenNextOutgoingBidirectionalStream() && !stream_requests_.empty() &&
crypto_stream_->encryption_established() && !goaway_received() &&
!going_away_ && connection()->connected()) {
StreamRequest* request = stream_requests_.front();
// TODO(ckrasic) - analyze data and then add logic to mark QUIC
// broken if wait times are excessive.
UMA_HISTOGRAM_TIMES("Net.QuicSession.PendingStreamsWaitTime",
tick_clock_->NowTicks() - request->pending_start_time_);
stream_requests_.pop_front();
request->OnRequestCompleteSuccess(
CreateOutgoingReliableStreamImpl(request->traffic_annotation())
->CreateHandle());
}
}
void QuicChromiumClientSession::OnConfigNegotiated() {
quic::QuicSpdyClientSessionBase::OnConfigNegotiated();
if (!stream_factory_ || !config()->HasReceivedAlternateServerAddress())
return;
// Server has sent an alternate address to connect to.
IPEndPoint new_address =
config()->ReceivedAlternateServerAddress().impl().socket_address();
IPEndPoint old_address;
GetDefaultSocket()->GetPeerAddress(&old_address);
// Migrate only if address families match, or if new address family is v6,
// since a v4 address should be reachable over a v6 network (using a
// v4-mapped v6 address).
if (old_address.GetFamily() != new_address.GetFamily() &&
old_address.GetFamily() == ADDRESS_FAMILY_IPV4) {
return;
}
if (old_address.GetFamily() != new_address.GetFamily()) {
DCHECK_EQ(old_address.GetFamily(), ADDRESS_FAMILY_IPV6);
DCHECK_EQ(new_address.GetFamily(), ADDRESS_FAMILY_IPV4);
// Use a v4-mapped v6 address.
new_address = IPEndPoint(ConvertIPv4ToIPv4MappedIPv6(new_address.address()),
new_address.port());
}
if (!stream_factory_->allow_server_migration())
return;
// Specifying kInvalidNetworkHandle for the |network| parameter
// causes the session to use the default network for the new socket.
Migrate(NetworkChangeNotifier::kInvalidNetworkHandle, new_address,
/*close_session_on_error=*/true, net_log_);
}
void QuicChromiumClientSession::OnCryptoHandshakeEvent(
CryptoHandshakeEvent event) {
if (!callback_.is_null() &&
(!require_confirmation_ || event == HANDSHAKE_CONFIRMED ||
event == ENCRYPTION_REESTABLISHED)) {
// TODO(rtenneti): Currently for all CryptoHandshakeEvent events, callback_
// could be called because there are no error events in CryptoHandshakeEvent
// enum. If error events are added to CryptoHandshakeEvent, then the
// following code needs to changed.
base::ResetAndReturn(&callback_).Run(OK);
}
if (event == HANDSHAKE_CONFIRMED) {
if (stream_factory_)
stream_factory_->set_require_confirmation(false);
// Update |connect_end| only when handshake is confirmed. This should also
// take care of any failed 0-RTT request.
connect_timing_.connect_end = tick_clock_->NowTicks();
DCHECK_LE(connect_timing_.connect_start, connect_timing_.connect_end);
UMA_HISTOGRAM_TIMES(
"Net.QuicSession.HandshakeConfirmedTime",
connect_timing_.connect_end - connect_timing_.connect_start);
// Track how long it has taken to finish handshake after we have finished
// DNS host resolution.
if (!connect_timing_.dns_end.is_null()) {
UMA_HISTOGRAM_TIMES(
"Net.QuicSession.HostResolution.HandshakeConfirmedTime",
tick_clock_->NowTicks() - connect_timing_.dns_end);
}
auto it = handles_.begin();
while (it != handles_.end()) {
Handle* handle = *it;
++it;
handle->OnCryptoHandshakeConfirmed();
}
NotifyRequestsOfConfirmation(OK);
// Attempt to migrate back to the default network after handshake has been
// confirmed if the session is not created on the default network.
if (migrate_session_on_network_change_v2_ &&
default_network_ != NetworkChangeNotifier::kInvalidNetworkHandle &&
GetDefaultSocket()->GetBoundNetwork() != default_network_) {
current_connection_migration_cause_ = ON_MIGRATE_BACK_TO_DEFAULT_NETWORK;
StartMigrateBackToDefaultNetworkTimer(
base::TimeDelta::FromSeconds(kMinRetryTimeForDefaultNetworkSecs));
}
}
quic::QuicSpdySession::OnCryptoHandshakeEvent(event);
}
void QuicChromiumClientSession::OnCryptoHandshakeMessageSent(
const quic::CryptoHandshakeMessage& message) {
logger_->OnCryptoHandshakeMessageSent(message);
}
void QuicChromiumClientSession::OnCryptoHandshakeMessageReceived(
const quic::CryptoHandshakeMessage& message) {
logger_->OnCryptoHandshakeMessageReceived(message);
if (message.tag() == quic::kREJ || message.tag() == quic::kSREJ) {
UMA_HISTOGRAM_CUSTOM_COUNTS("Net.QuicSession.RejectLength",
message.GetSerialized().length(), 1000, 10000,
50);
quic::QuicStringPiece proof;
UMA_HISTOGRAM_BOOLEAN("Net.QuicSession.RejectHasProof",
message.GetStringPiece(quic::kPROF, &proof));
}
}
void QuicChromiumClientSession::OnGoAway(const quic::QuicGoAwayFrame& frame) {
quic::QuicSession::OnGoAway(frame);
NotifyFactoryOfSessionGoingAway();
port_migration_detected_ =
frame.error_code == quic::QUIC_ERROR_MIGRATING_PORT;
}
void QuicChromiumClientSession::OnRstStream(
const quic::QuicRstStreamFrame& frame) {
quic::QuicSession::OnRstStream(frame);
}
void QuicChromiumClientSession::OnConnectionClosed(
quic::QuicErrorCode error,
const std::string& error_details,
quic::ConnectionCloseSource source) {
DCHECK(!connection()->connected());
logger_->OnConnectionClosed(error, error_details, source);
bool is_google_host = HasGoogleHost(GURL("https://" + session_key_.host()));
if (source == quic::ConnectionCloseSource::FROM_PEER) {
if (error == quic::QUIC_PUBLIC_RESET) {
// is_from_google_server will be true if the received EPID is
// kEPIDGoogleFrontEnd or kEPIDGoogleFrontEnd0.
const bool is_from_google_server =
error_details.find(base::StringPrintf(
"From %s", quic::kEPIDGoogleFrontEnd)) != std::string::npos;
if (IsCryptoHandshakeConfirmed()) {
UMA_HISTOGRAM_BOOLEAN(
"Net.QuicSession.ClosedByPublicReset.HandshakeConfirmed",
is_from_google_server);
} else {
UMA_HISTOGRAM_BOOLEAN("Net.QuicSession.ClosedByPublicReset",
is_from_google_server);
}
if (is_from_google_server) {
UMA_HISTOGRAM_COUNTS_100(
"Net.QuicSession.NumMigrationsExercisedBeforePublicReset",
sockets_.size() - 1);
}
}
if (IsCryptoHandshakeConfirmed()) {
if (is_google_host) {
base::UmaHistogramSparse(
"Net.QuicSession.ConnectionCloseErrorCodeServerGoogle."
"HandshakeConfirmed",
error);
}
base::UmaHistogramSparse(
"Net.QuicSession.ConnectionCloseErrorCodeServer.HandshakeConfirmed",
error);
base::HistogramBase* histogram = base::SparseHistogram::FactoryGet(
"Net.QuicSession.StreamCloseErrorCodeServer.HandshakeConfirmed",
base::HistogramBase::kUmaTargetedHistogramFlag);
size_t num_streams = GetNumActiveStreams();
if (num_streams > 0)
histogram->AddCount(error, num_streams);
}
if (is_google_host) {
base::UmaHistogramSparse(
"Net.QuicSession.ConnectionCloseErrorCodeServerGoogle", error);
}
base::UmaHistogramSparse("Net.QuicSession.ConnectionCloseErrorCodeServer",
error);
} else {
if (IsCryptoHandshakeConfirmed()) {
if (is_google_host) {
base::UmaHistogramSparse(
"Net.QuicSession.ConnectionCloseErrorCodeClientGoogle."
"HandshakeConfirmed",
error);
}
base::UmaHistogramSparse(
"Net.QuicSession.ConnectionCloseErrorCodeClient.HandshakeConfirmed",
error);
base::HistogramBase* histogram = base::SparseHistogram::FactoryGet(
"Net.QuicSession.StreamCloseErrorCodeClient.HandshakeConfirmed",
base::HistogramBase::kUmaTargetedHistogramFlag);
size_t num_streams = GetNumActiveStreams();
if (num_streams > 0)
histogram->AddCount(error, num_streams);
} else {
if (error == quic::QUIC_HANDSHAKE_TIMEOUT) {
UMA_HISTOGRAM_BOOLEAN(
"Net.QuicSession.HandshakeTimeout.PathDegradingDetected",
connection()->IsPathDegrading());
}
}
if (is_google_host) {
base::UmaHistogramSparse(
"Net.QuicSession.ConnectionCloseErrorCodeClientGoogle", error);
}
base::UmaHistogramSparse("Net.QuicSession.ConnectionCloseErrorCodeClient",
error);
if (error == quic::QUIC_TOO_MANY_RTOS) {
UMA_HISTOGRAM_COUNTS_1000(
"Net.QuicSession.ClosedByRtoAtClient.ReceivedPacketCount",
connection()->GetStats().packets_received);
UMA_HISTOGRAM_COUNTS_1000(
"Net.QuicSession.ClosedByRtoAtClient.SentPacketCount",
connection()->GetStats().packets_sent);
}
}
if (error == quic::QUIC_NETWORK_IDLE_TIMEOUT) {
UMA_HISTOGRAM_COUNTS_1M(
"Net.QuicSession.ConnectionClose.NumOpenStreams.TimedOut",
GetNumOpenOutgoingStreams());
if (IsCryptoHandshakeConfirmed()) {
if (GetNumOpenOutgoingStreams() > 0) {
UMA_HISTOGRAM_BOOLEAN(
"Net.QuicSession.TimedOutWithOpenStreams.HasUnackedPackets",
connection()->sent_packet_manager().HasInFlightPackets());
UMA_HISTOGRAM_COUNTS_1M(
"Net.QuicSession.TimedOutWithOpenStreams.ConsecutiveRTOCount",
connection()->sent_packet_manager().GetConsecutiveRtoCount());
UMA_HISTOGRAM_COUNTS_1M(
"Net.QuicSession.TimedOutWithOpenStreams.ConsecutiveTLPCount",
connection()->sent_packet_manager().GetConsecutiveTlpCount());
base::UmaHistogramSparse(
"Net.QuicSession.TimedOutWithOpenStreams.LocalPort",
connection()->self_address().port());
}
} else {
UMA_HISTOGRAM_COUNTS_1M(
"Net.QuicSession.ConnectionClose.NumOpenStreams.HandshakeTimedOut",
GetNumOpenOutgoingStreams());
UMA_HISTOGRAM_COUNTS_1M(
"Net.QuicSession.ConnectionClose.NumTotalStreams.HandshakeTimedOut",
num_total_streams_);
}
}
if (IsCryptoHandshakeConfirmed()) {
// QUIC connections should not timeout while there are open streams,
// since PING frames are sent to prevent timeouts. If, however, the
// connection timed out with open streams then QUIC traffic has become
// blackholed. Alternatively, if too many retransmission timeouts occur
// then QUIC traffic has become blackholed.
if (stream_factory_ && (error == quic::QUIC_TOO_MANY_RTOS ||
(error == quic::QUIC_NETWORK_IDLE_TIMEOUT &&
GetNumOpenOutgoingStreams() > 0))) {
stream_factory_->OnBlackholeAfterHandshakeConfirmed(this);
}
} else {
if (error == quic::QUIC_PUBLIC_RESET) {
RecordHandshakeFailureReason(HANDSHAKE_FAILURE_PUBLIC_RESET);
} else if (connection()->GetStats().packets_received == 0) {
RecordHandshakeFailureReason(HANDSHAKE_FAILURE_BLACK_HOLE);
base::UmaHistogramSparse(
"Net.QuicSession.ConnectionClose.HandshakeFailureBlackHole.QuicError",
error);
} else {
RecordHandshakeFailureReason(HANDSHAKE_FAILURE_UNKNOWN);
base::UmaHistogramSparse(
"Net.QuicSession.ConnectionClose.HandshakeFailureUnknown.QuicError",
error);
}
}
base::UmaHistogramSparse("Net.QuicSession.QuicVersion",
connection()->transport_version());
NotifyFactoryOfSessionGoingAway();
quic::QuicSession::OnConnectionClosed(error, error_details, source);
if (!callback_.is_null()) {
base::ResetAndReturn(&callback_).Run(ERR_QUIC_PROTOCOL_ERROR);
}
for (auto& socket : sockets_) {
socket->Close();
}
DCHECK(dynamic_streams().empty());
CloseAllStreams(ERR_UNEXPECTED);
CloseAllHandles(ERR_UNEXPECTED);
CancelAllRequests(ERR_CONNECTION_CLOSED);
NotifyRequestsOfConfirmation(ERR_CONNECTION_CLOSED);
NotifyFactoryOfSessionClosedLater();
}
void QuicChromiumClientSession::OnSuccessfulVersionNegotiation(
const quic::ParsedQuicVersion& version) {
logger_->OnSuccessfulVersionNegotiation(version);
quic::QuicSpdySession::OnSuccessfulVersionNegotiation(version);
}
void QuicChromiumClientSession::OnConnectivityProbeReceived(
const quic::QuicSocketAddress& self_address,
const quic::QuicSocketAddress& peer_address) {
DVLOG(1) << "Speculative probing response from ip:port: "
<< peer_address.ToString()
<< " to ip:port: " << self_address.ToString() << " is received";
// Notify the probing manager that a connectivity probing packet is received.
probing_manager_.OnConnectivityProbingReceived(self_address, peer_address);
}
int QuicChromiumClientSession::HandleWriteError(
int error_code,
scoped_refptr<QuicChromiumPacketWriter::ReusableIOBuffer> packet) {
current_connection_migration_cause_ = ON_WRITE_ERROR;
LogHandshakeStatusOnConnectionMigrationSignal();
base::UmaHistogramSparse("Net.QuicSession.WriteError", -error_code);
if (IsCryptoHandshakeConfirmed()) {
base::UmaHistogramSparse("Net.QuicSession.WriteError.HandshakeConfirmed",
-error_code);
}
if (error_code == ERR_MSG_TOO_BIG || stream_factory_ == nullptr ||
!migrate_session_on_network_change_v2_ || !IsCryptoHandshakeConfirmed()) {
return error_code;
}
NetworkChangeNotifier::NetworkHandle current_network =
GetDefaultSocket()->GetBoundNetwork();
net_log_.AddEvent(NetLogEventType::QUIC_CONNECTION_MIGRATION_ON_WRITE_ERROR,
NetLog::Int64Callback("network", current_network));
DCHECK(packet != nullptr);
DCHECK_NE(ERR_IO_PENDING, error_code);
DCHECK_GT(0, error_code);
DCHECK(packet_ == nullptr);
// Post a task to migrate the session onto a new network.
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&QuicChromiumClientSession::MigrateSessionOnWriteError,
weak_factory_.GetWeakPtr(), error_code,
connection()->writer()));
// Store packet in the session since the actual migration and packet rewrite
// can happen via this posted task or via an async network notification.
packet_ = std::move(packet);
ignore_read_error_ = true;
// Cause the packet writer to return ERR_IO_PENDING and block so
// that the actual migration happens from the message loop instead
// of under the call stack of quic::QuicConnection::WritePacket.
return ERR_IO_PENDING;
}
void QuicChromiumClientSession::MigrateSessionOnWriteError(
int error_code,
quic::QuicPacketWriter* writer) {
DCHECK(migrate_session_on_network_change_v2_);
// If |writer| is no longer actively in use, abort this migration attempt.
if (writer != connection()->writer())
return;
most_recent_write_error_timestamp_ = tick_clock_->NowTicks();
most_recent_write_error_ = error_code;
if (stream_factory_ == nullptr) {
// Close the connection if migration failed. Do not cause a
// connection close packet to be sent since socket may be borked.
connection()->CloseConnection(quic::QUIC_PACKET_WRITE_ERROR,
"Write error with nulled stream factory",
quic::ConnectionCloseBehavior::SILENT_CLOSE);
return;
}
current_connection_migration_cause_ = ON_WRITE_ERROR;
if (migrate_idle_session_ && CheckIdleTimeExceedsIdleMigrationPeriod())
return;
if (!migrate_idle_session_ && GetNumActiveStreams() == 0 &&
GetNumDrainingStreams() == 0) {
// connection close packet to be sent since socket may be borked.
connection()->CloseConnection(quic::QUIC_PACKET_WRITE_ERROR,
"Write error for non-migratable session",
quic::ConnectionCloseBehavior::SILENT_CLOSE);
return;
}
// Do not migrate if connection migration is disabled.
if (config()->DisableConnectionMigration()) {
HistogramAndLogMigrationFailure(
net_log_, MIGRATION_STATUS_DISABLED_BY_CONFIG, connection_id(),
"Migration disabled by config");
// Close the connection since migration was disabled. Do not cause a
// connection close packet to be sent since socket may be borked.
connection()->CloseConnection(quic::QUIC_PACKET_WRITE_ERROR,
"Write error for non-migratable session",
quic::ConnectionCloseBehavior::SILENT_CLOSE);
return;
}
NetworkChangeNotifier::NetworkHandle new_network =
stream_factory_->FindAlternateNetwork(
GetDefaultSocket()->GetBoundNetwork());
if (new_network == NetworkChangeNotifier::kInvalidNetworkHandle) {
// No alternate network found.
HistogramAndLogMigrationFailure(
net_log_, MIGRATION_STATUS_NO_ALTERNATE_NETWORK, connection_id(),
"No alternate network found");
OnNoNewNetwork();
return;
}
if (GetDefaultSocket()->GetBoundNetwork() == default_network_ &&
current_migrations_to_non_default_network_on_write_error_ >=
max_migrations_to_non_default_network_on_write_error_) {
HistogramAndLogMigrationFailure(
net_log_, MIGRATION_STATUS_ON_WRITE_ERROR_DISABLED, connection_id(),
"Exceeds maximum number of migrations on write error");
connection()->CloseConnection(
quic::QUIC_PACKET_WRITE_ERROR,
"Too many migrations for write error for the same network",
quic::ConnectionCloseBehavior::SILENT_CLOSE);
return;
}
current_migrations_to_non_default_network_on_write_error_++;
const NetLogWithSource migration_net_log = NetLogWithSource::Make(
net_log_.net_log(), NetLogSourceType::QUIC_CONNECTION_MIGRATION);
migration_net_log.BeginEvent(
NetLogEventType::QUIC_CONNECTION_MIGRATION_TRIGGERED,
NetLogQuicConnectionMigrationTriggerCallback("WriteError"));
MigrationResult result =
Migrate(new_network, connection()->peer_address().impl().socket_address(),
/*close_session_on_error=*/false, migration_net_log);
migration_net_log.EndEvent(
NetLogEventType::QUIC_CONNECTION_MIGRATION_TRIGGERED);
if (result == MigrationResult::FAILURE) {
// Close the connection if migration failed. Do not cause a
// connection close packet to be sent since socket may be borked.
connection()->CloseConnection(quic::QUIC_PACKET_WRITE_ERROR,
"Write and subsequent migration failed",
quic::ConnectionCloseBehavior::SILENT_CLOSE);
return;
}
if (new_network != default_network_) {
StartMigrateBackToDefaultNetworkTimer(
base::TimeDelta::FromSeconds(kMinRetryTimeForDefaultNetworkSecs));
} else {
CancelMigrateBackToDefaultNetworkTimer();
}
}
void QuicChromiumClientSession::OnNoNewNetwork() {
DCHECK(IsCryptoHandshakeConfirmed());
wait_for_new_network_ = true;
DVLOG(1) << "Force blocking the packet writer";
// Force blocking the packet writer to avoid any writes since there is no
// alternate network available.
static_cast<QuicChromiumPacketWriter*>(connection()->writer())
->set_force_write_blocked(true);
// Post a task to maybe close the session if the alarm fires.
task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&QuicChromiumClientSession::OnMigrationTimeout,
weak_factory_.GetWeakPtr(), sockets_.size()),
base::TimeDelta::FromSeconds(kWaitTimeForNewNetworkSecs));
}
void QuicChromiumClientSession::WriteToNewSocket() {
// Set |send_packet_after_migration_| to true so that a packet will be
// sent when the writer becomes unblocked.
send_packet_after_migration_ = true;
DVLOG(1) << "Cancel force blocking the packet writer";
// Notify writer that it is no longer forced blocked, which may call
// OnWriteUnblocked() if the writer has no write in progress.
static_cast<QuicChromiumPacketWriter*>(connection()->writer())
->set_force_write_blocked(false);
}
void QuicChromiumClientSession::OnMigrationTimeout(size_t num_sockets) {
// If number of sockets has changed, this migration task is stale.
if (num_sockets != sockets_.size())
return;
LogConnectionMigrationResultToHistogram(MIGRATION_STATUS_TIMEOUT);
CloseSessionOnError(ERR_NETWORK_CHANGED,
quic::QUIC_CONNECTION_MIGRATION_NO_NEW_NETWORK,
quic::ConnectionCloseBehavior::SILENT_CLOSE);
}
void QuicChromiumClientSession::OnProbeSucceeded(
NetworkChangeNotifier::NetworkHandle network,
const quic::QuicSocketAddress& peer_address,
const quic::QuicSocketAddress& self_address,
std::unique_ptr<DatagramClientSocket> socket,
std::unique_ptr<QuicChromiumPacketWriter> writer,
std::unique_ptr<QuicChromiumPacketReader> reader) {
DCHECK(socket);
DCHECK(writer);
DCHECK(reader);
net_log_.AddEvent(NetLogEventType::QUIC_SESSION_CONNECTIVITY_PROBING_FINISHED,
base::Bind(&NetLogProbingResultCallback, network,
&peer_address, /*is_success=*/true));
if (network != NetworkChangeNotifier::kInvalidNetworkHandle) {
OnProbeNetworkSucceeded(network, peer_address, self_address,
std::move(socket), std::move(writer),
std::move(reader));
}
}
void QuicChromiumClientSession::OnProbeNetworkSucceeded(
NetworkChangeNotifier::NetworkHandle network,
const quic::QuicSocketAddress& peer_address,
const quic::QuicSocketAddress& self_address,
std::unique_ptr<DatagramClientSocket> socket,
std::unique_ptr<QuicChromiumPacketWriter> writer,
std::unique_ptr<QuicChromiumPacketReader> reader) {
LogProbeResultToHistogram(current_connection_migration_cause_, true);
// Remove |this| as the old packet writer's delegate. Write error on old
// writers will be ignored.
// Set |this| to listen on socket write events on the packet writer
// that was used for probing.
static_cast<QuicChromiumPacketWriter*>(connection()->writer())
->set_delegate(nullptr);
writer->set_delegate(this);
connection()->SetSelfAddress(self_address);
// Close streams that are not migratable to the probed |network|.
ResetNonMigratableStreams();
if (!migrate_idle_session_ && GetNumActiveStreams() == 0 &&
GetNumDrainingStreams() == 0) {
// If idle sessions won't be migrated, close the connection.
CloseSessionOnErrorLater(
ERR_NETWORK_CHANGED,
quic::QUIC_CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS,
quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
return;
}
if (migrate_idle_session_ && CheckIdleTimeExceedsIdleMigrationPeriod())
return;
// Migrate to the probed socket immediately: socket, writer and reader will
// be acquired by connection and used as default on success.
if (!MigrateToSocket(std::move(socket), std::move(reader),
std::move(writer))) {
net_log_.AddEvent(
NetLogEventType::QUIC_CONNECTION_MIGRATION_FAILURE_AFTER_PROBING);
return;
}
net_log_.AddEvent(
NetLogEventType::QUIC_CONNECTION_MIGRATION_SUCCESS_AFTER_PROBING,
NetLog::Int64Callback("migrate_to_network", network));
if (network == default_network_) {
DVLOG(1) << "Client successfully migrated to default network.";
CancelMigrateBackToDefaultNetworkTimer();
return;
}
DVLOG(1) << "Client successfully got off default network after "
<< "successful probing network: " << network << ".";
current_migrations_to_non_default_network_on_path_degrading_++;
if (!migrate_back_to_default_timer_.IsRunning()) {
current_connection_migration_cause_ = ON_MIGRATE_BACK_TO_DEFAULT_NETWORK;
// Session gets off the |default_network|, stay on |network| for now but
// try to migrate back to default network after 1 second.
StartMigrateBackToDefaultNetworkTimer(
base::TimeDelta::FromSeconds(kMinRetryTimeForDefaultNetworkSecs));
}
}
void QuicChromiumClientSession::OnProbeFailed(
NetworkChangeNotifier::NetworkHandle network,
const quic::QuicSocketAddress& peer_address) {
net_log_.AddEvent(NetLogEventType::QUIC_SESSION_CONNECTIVITY_PROBING_FINISHED,
base::Bind(&NetLogProbingResultCallback, network,
&peer_address, /*is_success=*/false));
if (network != NetworkChangeNotifier::kInvalidNetworkHandle)
OnProbeNetworkFailed(network, peer_address);
}
void QuicChromiumClientSession::OnProbeNetworkFailed(
NetworkChangeNotifier::NetworkHandle network,
const quic::QuicSocketAddress& peer_address) {
LogProbeResultToHistogram(current_connection_migration_cause_, false);
// Probing failure can be ignored.
DVLOG(1) << "Connectivity probing failed on <network: " << network
<< ", peer_address: " << peer_address.ToString() << ">.";
DVLOG_IF(1, network == default_network_ &&
GetDefaultSocket()->GetBoundNetwork() != default_network_)
<< "Client probing failed on the default network, still using "
"non-default network.";
}
bool QuicChromiumClientSession::OnSendConnectivityProbingPacket(
QuicChromiumPacketWriter* writer,
const quic::QuicSocketAddress& peer_address) {
return connection()->SendConnectivityProbingPacket(writer, peer_address);
}
void QuicChromiumClientSession::OnNetworkConnected(
NetworkChangeNotifier::NetworkHandle network,
const NetLogWithSource& net_log) {
DCHECK(migrate_session_on_network_change_v2_);
net_log_.AddEvent(
NetLogEventType::QUIC_CONNECTION_MIGRATION_ON_NETWORK_CONNECTED,
NetLog::Int64Callback("connected_network", network));
// If there was no migration waiting for new network and the path is not
// degrading, ignore this signal.
if (!wait_for_new_network_ && !connection()->IsPathDegrading())
return;
if (connection()->IsPathDegrading()) {
current_connection_migration_cause_ = ON_PATH_DEGRADING;
}
if (wait_for_new_network_) {
wait_for_new_network_ = false;
if (current_connection_migration_cause_ == ON_WRITE_ERROR)
current_migrations_to_non_default_network_on_write_error_++;
// |wait_for_new_network_| is true, there was no working network previously.
// |network| is now the only possible candidate, migrate immediately.
MigrateNetworkImmediately(network);
} else {
// The connection is path degrading.
DCHECK(connection()->IsPathDegrading());
OnPathDegrading();
}
}
void QuicChromiumClientSession::OnNetworkDisconnectedV2(
NetworkChangeNotifier::NetworkHandle disconnected_network,
const NetLogWithSource& migration_net_log) {
DCHECK(migrate_session_on_network_change_v2_);
net_log_.AddEvent(
NetLogEventType::QUIC_CONNECTION_MIGRATION_ON_NETWORK_DISCONNECTED,
NetLog::Int64Callback("disconnected_network", disconnected_network));
LogMetricsOnNetworkDisconnected();
// Stop probing the disconnected network if there is one.
probing_manager_.CancelProbing(disconnected_network, peer_address());
if (disconnected_network == default_network_) {
DVLOG(1) << "Default network: " << default_network_ << " is disconnected.";
default_network_ = NetworkChangeNotifier::kInvalidNetworkHandle;
current_migrations_to_non_default_network_on_write_error_ = 0;
}
// Ignore the signal if the current active network is not affected.
if (GetDefaultSocket()->GetBoundNetwork() != disconnected_network) {
DVLOG(1) << "Client's current default network is not affected by the "
<< "disconnected one.";
return;
}
current_connection_migration_cause_ = ON_NETWORK_DISCONNECTED;
LogHandshakeStatusOnConnectionMigrationSignal();
if (!IsCryptoHandshakeConfirmed()) {
// Close the connection if handshake is not confirmed. Migration before
// handshake is not allowed.
CloseSessionOnErrorLater(
ERR_NETWORK_CHANGED,
quic::QUIC_CONNECTION_MIGRATION_HANDSHAKE_UNCONFIRMED,
quic::ConnectionCloseBehavior::SILENT_CLOSE);
return;
}
// Attempt to find alternative network.
NetworkChangeNotifier::NetworkHandle new_network =
stream_factory_->FindAlternateNetwork(disconnected_network);
if (new_network == NetworkChangeNotifier::kInvalidNetworkHandle) {
OnNoNewNetwork();
return;
}
// Current network is being disconnected, migrate immediately to the
// alternative network.
MigrateNetworkImmediately(new_network);
}
void QuicChromiumClientSession::OnNetworkMadeDefault(
NetworkChangeNotifier::NetworkHandle new_network,
const NetLogWithSource& migration_net_log) {
DCHECK(migrate_session_on_network_change_v2_);
net_log_.AddEvent(
NetLogEventType::QUIC_CONNECTION_MIGRATION_ON_NETWORK_MADE_DEFAULT,
NetLog::Int64Callback("new_default_network", new_network));
LogMetricsOnNetworkMadeDefault();
DCHECK_NE(NetworkChangeNotifier::kInvalidNetworkHandle, new_network);
DVLOG(1) << "Network: " << new_network
<< " becomes default, old default: " << default_network_;
default_network_ = new_network;
current_connection_migration_cause_ = ON_NETWORK_MADE_DEFAULT;
current_migrations_to_non_default_network_on_write_error_ = 0;
current_migrations_to_non_default_network_on_path_degrading_ = 0;
// Simply cancel the timer to migrate back to the default network if session
// is already on the default network.
if (GetDefaultSocket()->GetBoundNetwork() == new_network) {
CancelMigrateBackToDefaultNetworkTimer();
HistogramAndLogMigrationFailure(
migration_net_log, MIGRATION_STATUS_ALREADY_MIGRATED, connection_id(),
"Already migrated on the new network");
return;
}
LogHandshakeStatusOnConnectionMigrationSignal();
// Stay on the current network. Try to migrate back to default network
// without any delay, which will start probing the new default network and
// migrate to the new network immediately on success.
StartMigrateBackToDefaultNetworkTimer(base::TimeDelta());
}
void QuicChromiumClientSession::MigrateNetworkImmediately(
NetworkChangeNotifier::NetworkHandle network) {
// There is no choice but to migrate to |network|. If any error encoutered,
// close the session. When migration succeeds:
// - if no longer on the default network, start timer to migrate back;
// - otherwise, it's brought to default network, cancel the running timer to
// migrate back.
if (!migrate_idle_session_ && GetNumActiveStreams() == 0 &&
GetNumDrainingStreams() == 0) {
HistogramAndLogMigrationFailure(net_log_,
MIGRATION_STATUS_NO_MIGRATABLE_STREAMS,
connection_id(), "No active streams");
CloseSessionOnErrorLater(
ERR_NETWORK_CHANGED,
quic::QUIC_CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS,
quic::ConnectionCloseBehavior::SILENT_CLOSE);
return;
}
if (migrate_idle_session_ && CheckIdleTimeExceedsIdleMigrationPeriod())
return;
// Do not migrate if connection migration is disabled.
if (config()->DisableConnectionMigration()) {
HistogramAndLogMigrationFailure(
net_log_, MIGRATION_STATUS_DISABLED_BY_CONFIG, connection_id(),
"Migration disabled by config");
CloseSessionOnErrorLater(ERR_NETWORK_CHANGED,
quic::QUIC_CONNECTION_MIGRATION_DISABLED_BY_CONFIG,
quic::ConnectionCloseBehavior::SILENT_CLOSE);
return;
}
if (network == GetDefaultSocket()->GetBoundNetwork()) {
HistogramAndLogMigrationFailure(net_log_, MIGRATION_STATUS_ALREADY_MIGRATED,
connection_id(),
"Already bound to new network");
return;
}
// Cancel probing on |network| if there is any.
probing_manager_.CancelProbing(network, peer_address());
MigrationResult result =
Migrate(network, connection()->peer_address().impl().socket_address(),
/*close_session_on_error=*/true, net_log_);
if (result == MigrationResult::FAILURE)
return;
if (network == default_network_) {
CancelMigrateBackToDefaultNetworkTimer();
return;
}
// TODO(zhongyi): reconsider this, maybe we just want to hear back
// We are forced to migrate to |network|, probably |default_network_| is
// not working, start to migrate back to default network after 1 secs.
StartMigrateBackToDefaultNetworkTimer(
base::TimeDelta::FromSeconds(kMinRetryTimeForDefaultNetworkSecs));
}
void QuicChromiumClientSession::OnWriteError(int error_code) {
DCHECK_NE(ERR_IO_PENDING, error_code);
DCHECK_GT(0, error_code);
connection()->OnWriteError(error_code);
}
void QuicChromiumClientSession::OnWriteUnblocked() {
DCHECK(!connection()->writer()->IsWriteBlocked());
// A new packet will be written after migration completes, unignore read
// errors.
if (ignore_read_error_)
ignore_read_error_ = false;
if (packet_) {
DCHECK(send_packet_after_migration_);
send_packet_after_migration_ = false;
static_cast<QuicChromiumPacketWriter*>(connection()->writer())
->WritePacketToSocket(std::move(packet_));
return;
}
// Unblock the connection, which may send queued packets.
connection()->OnCanWrite();
if (send_packet_after_migration_) {
send_packet_after_migration_ = false;
if (!connection()->writer()->IsWriteBlocked()) {
SendPing();
}
}
}
void QuicChromiumClientSession::OnPathDegrading() {
if (go_away_on_path_degrading_) {
net_log_.AddEvent(
NetLogEventType::QUIC_SESSION_CLIENT_GOAWAY_ON_PATH_DEGRADING);
NotifyFactoryOfSessionGoingAway();
UMA_HISTOGRAM_COUNTS_1M(
"Net.QuicSession.ActiveStreamsOnGoAwayAfterPathDegrading",
GetNumActiveStreams());
UMA_HISTOGRAM_COUNTS_1M(
"Net.QuicSession.DrainingStreamsOnGoAwayAfterPathDegrading",
GetNumDrainingStreams());
return;
}
net_log_.AddEvent(
NetLogEventType::QUIC_CONNECTION_MIGRATION_ON_PATH_DEGRADING);
if (most_recent_path_degrading_timestamp_ == base::TimeTicks())
most_recent_path_degrading_timestamp_ = tick_clock_->NowTicks();
if (!stream_factory_)
return;
current_connection_migration_cause_ = ON_PATH_DEGRADING;
if (!migrate_session_early_v2_) {
HistogramAndLogMigrationFailure(
net_log_, MIGRATION_STATUS_PATH_DEGRADING_NOT_ENABLED, connection_id(),
"Migration on path degrading not enabled");
return;
}
if (GetDefaultSocket()->GetBoundNetwork() == default_network_ &&
current_migrations_to_non_default_network_on_path_degrading_ >=
max_migrations_to_non_default_network_on_path_degrading_) {
HistogramAndLogMigrationFailure(
net_log_, MIGRATION_STATUS_ON_PATH_DEGRADING_DISABLED, connection_id(),
"Exceeds maximum number of migrations on path degrading");
return;
}
NetworkChangeNotifier::NetworkHandle alternate_network =
stream_factory_->FindAlternateNetwork(
GetDefaultSocket()->GetBoundNetwork());
if (alternate_network == NetworkChangeNotifier::kInvalidNetworkHandle) {
HistogramAndLogMigrationFailure(
net_log_, MIGRATION_STATUS_NO_ALTERNATE_NETWORK, connection_id(),
"No alternative network on path degrading");
return;
}
LogHandshakeStatusOnConnectionMigrationSignal();
if (!IsCryptoHandshakeConfirmed()) {
HistogramAndLogMigrationFailure(
net_log_, MIGRATION_STATUS_PATH_DEGRADING_BEFORE_HANDSHAKE_CONFIRMED,
connection_id(), "Path degrading before handshake confirmed");
return;
}
const NetLogWithSource migration_net_log = NetLogWithSource::Make(
net_log_.net_log(), NetLogSourceType::QUIC_CONNECTION_MIGRATION);
migration_net_log.BeginEvent(
NetLogEventType::QUIC_CONNECTION_MIGRATION_TRIGGERED,
NetLogQuicConnectionMigrationTriggerCallback("PathDegrading"));
// Probe the alternative network, session will migrate to the probed
// network and decide whether it wants to migrate back to the default
// network on success.
StartProbeNetwork(alternate_network, peer_address(), migration_net_log);
migration_net_log.EndEvent(
NetLogEventType::QUIC_CONNECTION_MIGRATION_TRIGGERED);
}
bool QuicChromiumClientSession::ShouldKeepConnectionAlive() const {
return quic::QuicSpdySession::ShouldKeepConnectionAlive() ||
GetNumDrainingOutgoingStreams() > 0;
}
void QuicChromiumClientSession::OnProofValid(
const quic::QuicCryptoClientConfig::CachedState& cached) {
DCHECK(cached.proof_valid());
if (!server_info_) {
return;
}
QuicServerInfo::State* state = server_info_->mutable_state();
state->server_config = cached.server_config();
state->source_address_token = cached.source_address_token();
state->cert_sct = cached.cert_sct();
state->chlo_hash = cached.chlo_hash();
state->server_config_sig = cached.signature();
state->certs = cached.certs();
server_info_->Persist();
}
void QuicChromiumClientSession::OnProofVerifyDetailsAvailable(
const quic::ProofVerifyDetails& verify_details) {
const ProofVerifyDetailsChromium* verify_details_chromium =
reinterpret_cast<const ProofVerifyDetailsChromium*>(&verify_details);
cert_verify_result_.reset(
new CertVerifyResult(verify_details_chromium->cert_verify_result));
pinning_failure_log_ = verify_details_chromium->pinning_failure_log;
std::unique_ptr<ct::CTVerifyResult> ct_verify_result_copy(
new ct::CTVerifyResult(verify_details_chromium->ct_verify_result));
ct_verify_result_ = std::move(ct_verify_result_copy);
logger_->OnCertificateVerified(*cert_verify_result_);
pkp_bypassed_ = verify_details_chromium->pkp_bypassed;
is_fatal_cert_error_ = verify_details_chromium->is_fatal_cert_error;
}
void QuicChromiumClientSession::StartReading() {
for (auto& packet_reader : packet_readers_) {
packet_reader->StartReading();
}
}
void QuicChromiumClientSession::CloseSessionOnError(
int net_error,
quic::QuicErrorCode quic_error,
quic::ConnectionCloseBehavior behavior) {
base::UmaHistogramSparse("Net.QuicSession.CloseSessionOnError", -net_error);
if (!callback_.is_null()) {
base::ResetAndReturn(&callback_).Run(net_error);
}
CloseAllStreams(net_error);
net_log_.AddEvent(NetLogEventType::QUIC_SESSION_CLOSE_ON_ERROR,
NetLog::IntCallback("net_error", net_error));
if (connection()->connected())
connection()->CloseConnection(quic_error, "net error", behavior);
DCHECK(!connection()->connected());
CloseAllHandles(net_error);
NotifyFactoryOfSessionClosed();
}
void QuicChromiumClientSession::CloseSessionOnErrorLater(
int net_error,
quic::QuicErrorCode quic_error,
quic::ConnectionCloseBehavior behavior) {
base::UmaHistogramSparse("Net.QuicSession.CloseSessionOnError", -net_error);
if (!callback_.is_null()) {
base::ResetAndReturn(&callback_).Run(net_error);
}
CloseAllStreams(net_error);
CloseAllHandles(net_error);
net_log_.AddEvent(NetLogEventType::QUIC_SESSION_CLOSE_ON_ERROR,
NetLog::IntCallback("net_error", net_error));
if (connection()->connected())
connection()->CloseConnection(quic_error, "net error", behavior);
DCHECK(!connection()->connected());
NotifyFactoryOfSessionClosedLater();
}
void QuicChromiumClientSession::CloseAllStreams(int net_error) {
while (!dynamic_streams().empty()) {
quic::QuicStream* stream = dynamic_streams().begin()->second.get();
quic::QuicStreamId id = stream->id();
static_cast<QuicChromiumClientStream*>(stream)->OnError(net_error);
CloseStream(id);
}
}
void QuicChromiumClientSession::CloseAllHandles(int net_error) {
while (!handles_.empty()) {
Handle* handle = *handles_.begin();
handles_.erase(handle);
handle->OnSessionClosed(connection()->transport_version(), net_error,
error(), port_migration_detected_,
GetConnectTiming(), WasConnectionEverUsed());
}
}
void QuicChromiumClientSession::CancelAllRequests(int net_error) {
UMA_HISTOGRAM_COUNTS_1000("Net.QuicSession.AbortedPendingStreamRequests",
stream_requests_.size());
while (!stream_requests_.empty()) {
StreamRequest* request = stream_requests_.front();
stream_requests_.pop_front();
request->OnRequestCompleteFailure(net_error);
}
}
void QuicChromiumClientSession::NotifyRequestsOfConfirmation(int net_error) {
// Post tasks to avoid reentrancy.
for (auto& callback : waiting_for_confirmation_callbacks_)
task_runner_->PostTask(FROM_HERE,
base::BindOnce(std::move(callback), net_error));
waiting_for_confirmation_callbacks_.clear();
}
ProbingResult QuicChromiumClientSession::StartProbeNetwork(
NetworkChangeNotifier::NetworkHandle network,
const quic::QuicSocketAddress& peer_address,
const NetLogWithSource& migration_net_log) {
if (!stream_factory_)
return ProbingResult::FAILURE;
CHECK_NE(NetworkChangeNotifier::kInvalidNetworkHandle, network);
if (!migrate_idle_session_ && GetNumActiveStreams() == 0 &&
GetNumDrainingStreams() == 0) {
HistogramAndLogMigrationFailure(migration_net_log,
MIGRATION_STATUS_NO_MIGRATABLE_STREAMS,
connection_id(), "No active streams");
CloseSessionOnErrorLater(
ERR_NETWORK_CHANGED,
quic::QUIC_CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS,
quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
return ProbingResult::DISABLED_WITH_IDLE_SESSION;
}
if (migrate_idle_session_ && CheckIdleTimeExceedsIdleMigrationPeriod())
return ProbingResult::DISABLED_WITH_IDLE_SESSION;
// Abort probing if connection migration is disabled by config.
if (config()->DisableConnectionMigration()) {
DVLOG(1) << "Client disables probing network with connection migration "
<< "disabled by config";
HistogramAndLogMigrationFailure(
migration_net_log, MIGRATION_STATUS_DISABLED_BY_CONFIG, connection_id(),
"Migration disabled by config");
return ProbingResult::DISABLED_BY_CONFIG;
}
// Check if probing manager is probing the same path.
if (probing_manager_.IsUnderProbing(network, peer_address))
return ProbingResult::PENDING;
// Create and configure socket on |network|.
std::unique_ptr<DatagramClientSocket> probing_socket =
stream_factory_->CreateSocket(net_log_.net_log(), net_log_.source());
if (stream_factory_->ConfigureSocket(
probing_socket.get(), peer_address.impl().socket_address(), network,
session_key_.socket_tag()) != OK) {
HistogramAndLogMigrationFailure(
migration_net_log, MIGRATION_STATUS_INTERNAL_ERROR, connection_id(),
"Socket configuration failed");
return ProbingResult::INTERNAL_ERROR;
}
// Create new packet writer and reader on the probing socket.
std::unique_ptr<QuicChromiumPacketWriter> probing_writer(
new QuicChromiumPacketWriter(probing_socket.get(), task_runner_));
std::unique_ptr<QuicChromiumPacketReader> probing_reader(
new QuicChromiumPacketReader(probing_socket.get(), clock_, this,
yield_after_packets_, yield_after_duration_,
net_log_));
int rtt_ms = connection()
->sent_packet_manager()
.GetRttStats()
->smoothed_rtt()
.ToMilliseconds();
if (rtt_ms == 0 || rtt_ms > kDefaultRTTMilliSecs)
rtt_ms = kDefaultRTTMilliSecs;
int timeout_ms = rtt_ms * 2;
probing_manager_.StartProbing(
network, peer_address, std::move(probing_socket),
std::move(probing_writer), std::move(probing_reader),
base::TimeDelta::FromMilliseconds(timeout_ms), net_log_);
return ProbingResult::PENDING;
}
void QuicChromiumClientSession::StartMigrateBackToDefaultNetworkTimer(
base::TimeDelta delay) {
if (current_connection_migration_cause_ != ON_NETWORK_MADE_DEFAULT)
current_connection_migration_cause_ = ON_MIGRATE_BACK_TO_DEFAULT_NETWORK;
CancelMigrateBackToDefaultNetworkTimer();
// Post a task to try migrate back to default network after |delay|.
migrate_back_to_default_timer_.Start(
FROM_HERE, delay,
base::Bind(
&QuicChromiumClientSession::MaybeRetryMigrateBackToDefaultNetwork,
weak_factory_.GetWeakPtr()));
}
void QuicChromiumClientSession::CancelMigrateBackToDefaultNetworkTimer() {
retry_migrate_back_count_ = 0;
migrate_back_to_default_timer_.Stop();
}
void QuicChromiumClientSession::TryMigrateBackToDefaultNetwork(
base::TimeDelta timeout) {
if (default_network_ == NetworkChangeNotifier::kInvalidNetworkHandle) {
DVLOG(1) << "Default network is not connected";
return;
}
net_log_.AddEvent(NetLogEventType::QUIC_CONNECTION_MIGRATION_ON_MIGRATE_BACK,
base::Bind(NetLog::Int64Callback(
"retry_count", retry_migrate_back_count_)));
// Start probe default network immediately, if manager is probing
// the same network, this will be a no-op. Otherwise, previous probe
// will be cancelled and manager starts to probe |default_network_|
// immediately.
ProbingResult result =
StartProbeNetwork(default_network_, peer_address(), net_log_);
if (result == ProbingResult::DISABLED_WITH_IDLE_SESSION)
return;
if (result != ProbingResult::PENDING) {
// Session is not allowed to migrate, mark session as going away, cancel
// migrate back to default timer.
NotifyFactoryOfSessionGoingAway();
CancelMigrateBackToDefaultNetworkTimer();
return;
}
retry_migrate_back_count_++;
migrate_back_to_default_timer_.Start(
FROM_HERE, timeout,
base::Bind(
&QuicChromiumClientSession::MaybeRetryMigrateBackToDefaultNetwork,
weak_factory_.GetWeakPtr()));
}
void QuicChromiumClientSession::MaybeRetryMigrateBackToDefaultNetwork() {
base::TimeDelta retry_migrate_back_timeout =
base::TimeDelta::FromSeconds(UINT64_C(1) << retry_migrate_back_count_);
if (default_network_ == GetDefaultSocket()->GetBoundNetwork()) {
// If session has been back on the default already by other direct
// migration attempt, cancel migrate back now.
CancelMigrateBackToDefaultNetworkTimer();
return;
}
if (retry_migrate_back_timeout > max_time_on_non_default_network_) {
// Mark session as going away to accept no more streams.
NotifyFactoryOfSessionGoingAway();
return;
}
TryMigrateBackToDefaultNetwork(retry_migrate_back_timeout);
}
bool QuicChromiumClientSession::CheckIdleTimeExceedsIdleMigrationPeriod() {
if (!migrate_idle_session_)
return false;
if (GetNumActiveStreams() != 0 || GetNumDrainingStreams() != 0) {
return false;
}
// There are no active/drainning streams, check the last stream's finish time.
if (tick_clock_->NowTicks() - most_recent_stream_close_time_ <
idle_migration_period_) {
// Still within the idle migration period.
return false;
}
HistogramAndLogMigrationFailure(
net_log_, MIGRATION_STATUS_IDLE_MIGRATION_TIMEOUT, connection_id(),
"Ilde migration period exceeded");
CloseSessionOnErrorLater(ERR_NETWORK_CHANGED, quic::QUIC_NETWORK_IDLE_TIMEOUT,
quic::ConnectionCloseBehavior::SILENT_CLOSE);
return true;
}
void QuicChromiumClientSession::ResetNonMigratableStreams() {
// TODO(zhongyi): may close non-migratable draining streams as well to avoid
// sending additional data on alternate networks.
auto it = dynamic_streams().begin();
// Stream may be deleted when iterating through the map.
while (it != dynamic_streams().end()) {
QuicChromiumClientStream* stream =
static_cast<QuicChromiumClientStream*>(it->second.get());
if (!stream->can_migrate_to_cellular_network()) {
// Close the stream in both direction by resetting the stream.
// TODO(zhongyi): use a different error code to reset streams for
// connection migration.
stream->Reset(quic::QUIC_STREAM_CANCELLED);
} else {
it++;
}
}
}
void QuicChromiumClientSession::LogMetricsOnNetworkDisconnected() {
if (most_recent_path_degrading_timestamp_ != base::TimeTicks()) {
most_recent_network_disconnected_timestamp_ = tick_clock_->NowTicks();
base::TimeDelta degrading_duration =
most_recent_network_disconnected_timestamp_ -
most_recent_path_degrading_timestamp_;
UMA_HISTOGRAM_CUSTOM_TIMES(
"Net.QuicNetworkDegradingDurationTillDisconnected", degrading_duration,
base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(10),
100);
}
if (most_recent_write_error_timestamp_ != base::TimeTicks()) {
base::TimeDelta write_error_to_disconnection_gap =
most_recent_network_disconnected_timestamp_ -
most_recent_write_error_timestamp_;
UMA_HISTOGRAM_CUSTOM_TIMES(
"Net.QuicNetworkGapBetweenWriteErrorAndDisconnection",
write_error_to_disconnection_gap, base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromMinutes(10), 100);
base::UmaHistogramSparse("Net.QuicSession.WriteError.NetworkDisconnected",
-most_recent_write_error_);
most_recent_write_error_ = 0;
most_recent_write_error_timestamp_ = base::TimeTicks();
}
}
void QuicChromiumClientSession::LogMetricsOnNetworkMadeDefault() {
if (most_recent_path_degrading_timestamp_ != base::TimeTicks()) {
if (most_recent_network_disconnected_timestamp_ != base::TimeTicks()) {
// NetworkDiscconected happens before NetworkMadeDefault, the platform
// is dropping WiFi.
base::TimeTicks now = tick_clock_->NowTicks();
base::TimeDelta disconnection_duration =
now - most_recent_network_disconnected_timestamp_;
base::TimeDelta degrading_duration =
now - most_recent_path_degrading_timestamp_;
UMA_HISTOGRAM_CUSTOM_TIMES("Net.QuicNetworkDisconnectionDuration",
disconnection_duration,
base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromMinutes(10), 100);
UMA_HISTOGRAM_CUSTOM_TIMES(
"Net.QuicNetworkDegradingDurationTillNewNetworkMadeDefault",
degrading_duration, base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromMinutes(10), 100);
most_recent_network_disconnected_timestamp_ = base::TimeTicks();
}
most_recent_path_degrading_timestamp_ = base::TimeTicks();
}
}
void QuicChromiumClientSession::LogConnectionMigrationResultToHistogram(
QuicConnectionMigrationStatus status) {
UMA_HISTOGRAM_ENUMERATION("Net.QuicSession.ConnectionMigration", status,
MIGRATION_STATUS_MAX);
// Log the connection migraiton result to different histograms based on the
// cause of the connection migration.
std::string histogram_name =
"Net.QuicSession.ConnectionMigration." +
ConnectionMigrationCauseToString(current_connection_migration_cause_);
base::UmaHistogramEnumeration(histogram_name, status, MIGRATION_STATUS_MAX);
current_connection_migration_cause_ = UNKNOWN_CAUSE;
}
void QuicChromiumClientSession::LogHandshakeStatusOnConnectionMigrationSignal()
const {
UMA_HISTOGRAM_BOOLEAN("Net.QuicSession.HandshakeStatusOnConnectionMigration",
IsCryptoHandshakeConfirmed());
const std::string histogram_name =
"Net.QuicSession.HandshakeStatusOnConnectionMigration." +
ConnectionMigrationCauseToString(current_connection_migration_cause_);
STATIC_HISTOGRAM_POINTER_GROUP(
histogram_name, current_connection_migration_cause_, MIGRATION_CAUSE_MAX,
AddBoolean(IsCryptoHandshakeConfirmed()),
base::BooleanHistogram::FactoryGet(
histogram_name, base::HistogramBase::kUmaTargetedHistogramFlag));
}
void QuicChromiumClientSession::HistogramAndLogMigrationFailure(
const NetLogWithSource& net_log,
QuicConnectionMigrationStatus status,
quic::QuicConnectionId connection_id,
const std::string& reason) {
LogConnectionMigrationResultToHistogram(status);
net_log.AddEvent(NetLogEventType::QUIC_CONNECTION_MIGRATION_FAILURE,
base::Bind(&NetLogQuicConnectionMigrationFailureCallback,
connection_id, reason));
}
void QuicChromiumClientSession::HistogramAndLogMigrationSuccess(
const NetLogWithSource& net_log,
quic::QuicConnectionId connection_id) {
LogConnectionMigrationResultToHistogram(MIGRATION_STATUS_SUCCESS);
net_log.AddEvent(
NetLogEventType::QUIC_CONNECTION_MIGRATION_SUCCESS,
base::Bind(&NetLogQuicConnectionMigrationSuccessCallback, connection_id));
}
std::unique_ptr<base::Value> QuicChromiumClientSession::GetInfoAsValue(
const std::set<HostPortPair>& aliases) {
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
dict->SetString("version",
QuicVersionToString(connection()->transport_version()));
dict->SetInteger("open_streams", GetNumOpenOutgoingStreams());
std::unique_ptr<base::ListValue> stream_list(new base::ListValue());
for (DynamicStreamMap::const_iterator it = dynamic_streams().begin();
it != dynamic_streams().end(); ++it) {
stream_list->AppendString(base::NumberToString(it->second->id()));
}
dict->Set("active_streams", std::move(stream_list));
dict->SetInteger("total_streams", num_total_streams_);
dict->SetString("peer_address", peer_address().ToString());
dict->SetString("connection_id", connection_id().ToString());
dict->SetBoolean("connected", connection()->connected());
const quic::QuicConnectionStats& stats = connection()->GetStats();
dict->SetInteger("packets_sent", stats.packets_sent);
dict->SetInteger("packets_received", stats.packets_received);
dict->SetInteger("packets_lost", stats.packets_lost);
SSLInfo ssl_info;
std::unique_ptr<base::ListValue> alias_list(new base::ListValue());
for (const auto& alias : aliases) {
alias_list->AppendString(alias.ToString());
}
dict->Set("aliases", std::move(alias_list));
return std::move(dict);
}
std::unique_ptr<QuicChromiumClientSession::Handle>
QuicChromiumClientSession::CreateHandle(const HostPortPair& destination) {
return std::make_unique<QuicChromiumClientSession::Handle>(
weak_factory_.GetWeakPtr(), destination);
}
void QuicChromiumClientSession::OnReadError(
int result,
const DatagramClientSocket* socket) {
DCHECK(socket != nullptr);
base::UmaHistogramSparse("Net.QuicSession.ReadError.AnyNetwork", -result);
if (socket != GetDefaultSocket()) {
DVLOG(1) << "Ignore read error on old sockets";
base::UmaHistogramSparse("Net.QuicSession.ReadError.OtherNetworks",
-result);
// Ignore read errors from sockets that are not affecting the current
// network, i.e., sockets that are no longer active and probing socket.
// TODO(jri): Maybe clean up old sockets on error.
return;
}
base::UmaHistogramSparse("Net.QuicSession.ReadError.CurrentNetwork", -result);
if (IsCryptoHandshakeConfirmed()) {
base::UmaHistogramSparse(
"Net.QuicSession.ReadError.CurrentNetwork.HandshakeConfirmed", -result);
}
if (ignore_read_error_) {
DVLOG(1) << "Ignore read error.";
// Ignore read errors during pending migration. Connection will be closed if
// pending migration failed or timed out.
base::UmaHistogramSparse("Net.QuicSession.ReadError.PendingMigration",
-result);
return;
}
DVLOG(1) << "Closing session on read error: " << result;
connection()->CloseConnection(quic::QUIC_PACKET_READ_ERROR,
ErrorToString(result),
quic::ConnectionCloseBehavior::SILENT_CLOSE);
}
bool QuicChromiumClientSession::OnPacket(
const quic::QuicReceivedPacket& packet,
const quic::QuicSocketAddress& local_address,
const quic::QuicSocketAddress& peer_address) {
ProcessUdpPacket(local_address, peer_address, packet);
if (!connection()->connected()) {
NotifyFactoryOfSessionClosedLater();
return false;
}
return true;
}
void QuicChromiumClientSession::NotifyFactoryOfSessionGoingAway() {
going_away_ = true;
if (stream_factory_)
stream_factory_->OnSessionGoingAway(this);
}
void QuicChromiumClientSession::NotifyFactoryOfSessionClosedLater() {
if (!dynamic_streams().empty())
RecordUnexpectedOpenStreams(NOTIFY_FACTORY_OF_SESSION_CLOSED_LATER);
if (!going_away_)
RecordUnexpectedNotGoingAway(NOTIFY_FACTORY_OF_SESSION_CLOSED_LATER);
going_away_ = true;
DCHECK_EQ(0u, GetNumActiveStreams());
DCHECK(!connection()->connected());
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&QuicChromiumClientSession::NotifyFactoryOfSessionClosed,
weak_factory_.GetWeakPtr()));
}
void QuicChromiumClientSession::NotifyFactoryOfSessionClosed() {
if (!dynamic_streams().empty())
RecordUnexpectedOpenStreams(NOTIFY_FACTORY_OF_SESSION_CLOSED);
if (!going_away_)
RecordUnexpectedNotGoingAway(NOTIFY_FACTORY_OF_SESSION_CLOSED);
going_away_ = true;
DCHECK_EQ(0u, GetNumActiveStreams());
// Will delete |this|.
if (stream_factory_)
stream_factory_->OnSessionClosed(this);
}
MigrationResult QuicChromiumClientSession::Migrate(
NetworkChangeNotifier::NetworkHandle network,
IPEndPoint peer_address,
bool close_session_on_error,
const NetLogWithSource& migration_net_log) {
if (!stream_factory_)
return MigrationResult::FAILURE;
if (network != NetworkChangeNotifier::kInvalidNetworkHandle) {
// This is a migration attempt from connection migration.
ResetNonMigratableStreams();
if (!migrate_idle_session_ && GetNumActiveStreams() == 0 &&
GetNumDrainingStreams() == 0) {
// If idle sessions can not be migrated, close the session if needed.
if (close_session_on_error) {
CloseSessionOnErrorLater(
ERR_NETWORK_CHANGED,
quic::QUIC_CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS,
quic::ConnectionCloseBehavior::SILENT_CLOSE);
}
return MigrationResult::FAILURE;
}
}
// Create and configure socket on |network|.
std::unique_ptr<DatagramClientSocket> socket(
stream_factory_->CreateSocket(net_log_.net_log(), net_log_.source()));
if (stream_factory_->ConfigureSocket(socket.get(), peer_address, network,
session_key_.socket_tag()) != OK) {
HistogramAndLogMigrationFailure(
migration_net_log, MIGRATION_STATUS_INTERNAL_ERROR, connection_id(),
"Socket configuration failed");
if (close_session_on_error) {
if (migrate_session_on_network_change_v2_) {
CloseSessionOnErrorLater(ERR_NETWORK_CHANGED,
quic::QUIC_CONNECTION_MIGRATION_INTERNAL_ERROR,
quic::ConnectionCloseBehavior::SILENT_CLOSE);
} else {
CloseSessionOnError(ERR_NETWORK_CHANGED,
quic::QUIC_CONNECTION_MIGRATION_INTERNAL_ERROR,
quic::ConnectionCloseBehavior::SILENT_CLOSE);
}
}
return MigrationResult::FAILURE;
}
// Create new packet reader and writer on the new socket.
std::unique_ptr<QuicChromiumPacketReader> new_reader(
new QuicChromiumPacketReader(socket.get(), clock_, this,
yield_after_packets_, yield_after_duration_,
net_log_));
new_reader->StartReading();
std::unique_ptr<QuicChromiumPacketWriter> new_writer(
new QuicChromiumPacketWriter(socket.get(), task_runner_));
static_cast<QuicChromiumPacketWriter*>(connection()->writer())
->set_delegate(nullptr);
new_writer->set_delegate(this);
// Migrate to the new socket.
if (!MigrateToSocket(std::move(socket), std::move(new_reader),
std::move(new_writer))) {
HistogramAndLogMigrationFailure(migration_net_log,
MIGRATION_STATUS_TOO_MANY_CHANGES,
connection_id(), "Too many changes");
if (close_session_on_error) {
if (migrate_session_on_network_change_v2_) {
CloseSessionOnErrorLater(
ERR_NETWORK_CHANGED,
quic::QUIC_CONNECTION_MIGRATION_TOO_MANY_CHANGES,
quic::ConnectionCloseBehavior::SILENT_CLOSE);
} else {
CloseSessionOnError(ERR_NETWORK_CHANGED,
quic::QUIC_CONNECTION_MIGRATION_TOO_MANY_CHANGES,
quic::ConnectionCloseBehavior::SILENT_CLOSE);
}
}
return MigrationResult::FAILURE;
}
HistogramAndLogMigrationSuccess(migration_net_log, connection_id());
return MigrationResult::SUCCESS;
}
bool QuicChromiumClientSession::MigrateToSocket(
std::unique_ptr<DatagramClientSocket> socket,
std::unique_ptr<QuicChromiumPacketReader> reader,
std::unique_ptr<QuicChromiumPacketWriter> writer) {
DCHECK_EQ(sockets_.size(), packet_readers_.size());
// TODO(zhongyi): figure out whether we want to limit the number of
// connection migrations for v2, which includes migration on platform signals,
// write error events, and path degrading on original network.
if (!migrate_session_on_network_change_v2_ &&
sockets_.size() >= kMaxReadersPerQuicSession) {
return false;
}
packet_readers_.push_back(std::move(reader));
sockets_.push_back(std::move(socket));
// Froce the writer to be blocked to prevent it being used until
// WriteToNewSocket completes.
DVLOG(1) << "Force blocking the packet writer";
writer->set_force_write_blocked(true);
// TODO(jri): Make SetQuicPacketWriter take a scoped_ptr.
connection()->SetQuicPacketWriter(writer.release(), /*owns_writer=*/true);
// Post task to write the pending packet or a PING packet to the new
// socket. This avoids reentrancy issues if there is a write error
// on the write to the new socket.
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&QuicChromiumClientSession::WriteToNewSocket,
weak_factory_.GetWeakPtr()));
return true;
}
void QuicChromiumClientSession::PopulateNetErrorDetails(
NetErrorDetails* details) const {
details->quic_port_migration_detected = port_migration_detected_;
details->quic_connection_error = error();
}
const DatagramClientSocket* QuicChromiumClientSession::GetDefaultSocket()
const {
DCHECK(sockets_.back().get() != nullptr);
// The most recently added socket is the currently active one.
return sockets_.back().get();
}
bool QuicChromiumClientSession::IsAuthorized(const std::string& hostname) {
bool result =
CanPool(hostname, session_key_.privacy_mode(), session_key_.socket_tag());
if (result)
streams_pushed_count_++;
return result;
}
bool QuicChromiumClientSession::HandlePromised(
quic::QuicStreamId id,
quic::QuicStreamId promised_id,
const spdy::SpdyHeaderBlock& headers) {
bool result =
quic::QuicSpdyClientSessionBase::HandlePromised(id, promised_id, headers);
if (result) {
// The push promise is accepted, notify the push_delegate that a push
// promise has been received.
if (push_delegate_) {
std::string pushed_url =
quic::SpdyUtils::GetPromisedUrlFromHeaders(headers);
push_delegate_->OnPush(std::make_unique<QuicServerPushHelper>(
weak_factory_.GetWeakPtr(), GURL(pushed_url)),
net_log_);
}
if (headers_include_h2_stream_dependency_) {
// Even though the promised stream will not be created until after the
// push promise headers are received, send a PRIORITY frame for the
// promised stream ID. Send |kDefaultPriority| since that will be the
// initial spdy::SpdyPriority of the push promise stream when created.
const spdy::SpdyPriority priority = quic::QuicStream::kDefaultPriority;
spdy::SpdyStreamId parent_stream_id = 0;
int weight = 0;
bool exclusive = false;
priority_dependency_state_.OnStreamCreation(
promised_id, priority, &parent_stream_id, &weight, &exclusive);
WritePriority(promised_id, parent_stream_id, weight, exclusive);
}
}
net_log_.AddEvent(NetLogEventType::QUIC_SESSION_PUSH_PROMISE_RECEIVED,
base::Bind(&NetLogQuicPushPromiseReceivedCallback, &headers,
id, promised_id));
return result;
}
void QuicChromiumClientSession::DeletePromised(
quic::QuicClientPromisedInfo* promised) {
if (IsOpenStream(promised->id()))
streams_pushed_and_claimed_count_++;
quic::QuicSpdyClientSessionBase::DeletePromised(promised);
}
void QuicChromiumClientSession::OnPushStreamTimedOut(
quic::QuicStreamId stream_id) {
quic::QuicSpdyStream* stream = GetPromisedStream(stream_id);
if (stream != nullptr)
bytes_pushed_and_unclaimed_count_ += stream->stream_bytes_read();
}
void QuicChromiumClientSession::CancelPush(const GURL& url) {
quic::QuicClientPromisedInfo* promised_info =
quic::QuicSpdyClientSessionBase::GetPromisedByUrl(url.spec());
if (!promised_info || promised_info->is_validating()) {
// Push stream has already been claimed or is pending matched to a request.
return;
}
quic::QuicStreamId stream_id = promised_info->id();
// Collect data on the cancelled push stream.
quic::QuicSpdyStream* stream = GetPromisedStream(stream_id);
if (stream != nullptr)
bytes_pushed_and_unclaimed_count_ += stream->stream_bytes_read();
// Send the reset and remove the promised info from the promise index.
quic::QuicSpdyClientSessionBase::ResetPromised(stream_id,
quic::QUIC_STREAM_CANCELLED);
DeletePromised(promised_info);
}
const LoadTimingInfo::ConnectTiming&
QuicChromiumClientSession::GetConnectTiming() {
connect_timing_.ssl_start = connect_timing_.connect_start;
connect_timing_.ssl_end = connect_timing_.connect_end;
return connect_timing_;
}
quic::QuicTransportVersion QuicChromiumClientSession::GetQuicVersion() const {
return connection()->transport_version();
}
size_t QuicChromiumClientSession::EstimateMemoryUsage() const {
// TODO(xunjieli): Estimate |crypto_stream_|, quic::QuicSpdySession's
// quic::QuicHeaderList, quic::QuicSession's QuiCWriteBlockedList, open
// streams and unacked packet map.
return base::trace_event::EstimateMemoryUsage(packet_readers_);
}
} // namespace net