|  | // 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 |