// Copyright 2012 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/http/http_stream_factory_job.h" #include #include #include #include #include "base/check_op.h" #include "base/containers/contains.h" #include "base/feature_list.h" #include "base/functional/bind.h" #include "base/functional/callback.h" #include "base/functional/callback_helpers.h" #include "base/location.h" #include "base/metrics/histogram_macros.h" #include "base/metrics/sparse_histogram.h" #include "base/notreached.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/task/single_thread_task_runner.h" #include "base/values.h" #include "build/build_config.h" #include "net/base/host_port_pair.h" #include "net/base/port_util.h" #include "net/base/proxy_delegate.h" #include "net/base/tracing.h" #include "net/cert/cert_verifier.h" #include "net/dns/public/secure_dns_policy.h" #include "net/http/bidirectional_stream_impl.h" #include "net/http/http_basic_stream.h" #include "net/http/http_network_session.h" #include "net/http/http_request_info.h" #include "net/http/http_server_properties.h" #include "net/http/http_stream_factory.h" #include "net/http/proxy_fallback.h" #include "net/log/net_log.h" #include "net/log/net_log_capture_mode.h" #include "net/log/net_log_event_type.h" #include "net/log/net_log_source.h" #include "net/log/net_log_source_type.h" #include "net/quic/bidirectional_stream_quic_impl.h" #include "net/quic/quic_http_stream.h" #include "net/socket/client_socket_handle.h" #include "net/socket/client_socket_pool_manager.h" #include "net/socket/connect_job.h" #include "net/socket/ssl_client_socket.h" #include "net/socket/stream_socket.h" #include "net/spdy/bidirectional_stream_spdy_impl.h" #include "net/spdy/http2_push_promise_index.h" #include "net/spdy/spdy_http_stream.h" #include "net/spdy/spdy_session.h" #include "net/ssl/ssl_cert_request_info.h" #include "net/third_party/quiche/src/quiche/spdy/core/spdy_protocol.h" #include "url/scheme_host_port.h" #include "url/url_constants.h" namespace net { namespace { // Experiment to preconnect only one connection if HttpServerProperties is // not supported or initialized. BASE_FEATURE(kLimitEarlyPreconnectsExperiment, "LimitEarlyPreconnects", base::FEATURE_ENABLED_BY_DEFAULT); } // namespace const char* NetLogHttpStreamJobType(HttpStreamFactory::JobType job_type) { switch (job_type) { case HttpStreamFactory::MAIN: return "main"; case HttpStreamFactory::ALTERNATIVE: return "alternative"; case HttpStreamFactory::DNS_ALPN_H3: return "dns_alpn_h3"; case HttpStreamFactory::PRECONNECT: return "preconnect"; case HttpStreamFactory::PRECONNECT_DNS_ALPN_H3: return "preconnect_dns_alpn_h3"; } return ""; } // Returns parameters associated with the start of a HTTP stream job. base::Value::Dict NetLogHttpStreamJobParams(const NetLogSource& source, const GURL& original_url, const GURL& url, bool expect_spdy, bool using_quic, HttpStreamFactory::JobType job_type, RequestPriority priority) { base::Value::Dict dict; if (source.IsValid()) source.AddToEventParameters(dict); dict.Set("original_url", original_url.DeprecatedGetOriginAsURL().spec()); dict.Set("url", url.DeprecatedGetOriginAsURL().spec()); dict.Set("expect_spdy", expect_spdy); dict.Set("using_quic", using_quic); dict.Set("priority", RequestPriorityToString(priority)); dict.Set("type", NetLogHttpStreamJobType(job_type)); return dict; } // Returns parameters associated with the ALPN protocol of a HTTP stream. base::Value::Dict NetLogHttpStreamProtoParams(NextProto negotiated_protocol) { base::Value::Dict dict; dict.Set("proto", NextProtoToString(negotiated_protocol)); return dict; } HttpStreamFactory::Job::Job(Delegate* delegate, JobType job_type, HttpNetworkSession* session, const HttpRequestInfo& request_info, RequestPriority priority, const ProxyInfo& proxy_info, const SSLConfig& server_ssl_config, const SSLConfig& proxy_ssl_config, url::SchemeHostPort destination, GURL origin_url, NextProto alternative_protocol, quic::ParsedQuicVersion quic_version, bool is_websocket, bool enable_ip_based_pooling, NetLog* net_log) : request_info_(request_info), priority_(priority), proxy_info_(proxy_info), server_ssl_config_(server_ssl_config), proxy_ssl_config_(proxy_ssl_config), net_log_( NetLogWithSource::Make(net_log, NetLogSourceType::HTTP_STREAM_JOB)), io_callback_( base::BindRepeating(&Job::OnIOComplete, base::Unretained(this))), connection_(std::make_unique()), session_(session), destination_(std::move(destination)), origin_url_(origin_url), is_websocket_(is_websocket), try_websocket_over_http2_( is_websocket_ && origin_url_.SchemeIs(url::kWssScheme) && // TODO(https://crbug.com/1277306): Remove the proxy check. proxy_info_.is_direct()), // Don't use IP connection pooling for HTTP over HTTPS proxies. It doesn't // get us much, and testing it is more effort than its worth. enable_ip_based_pooling_( enable_ip_based_pooling && !(proxy_info_.proxy_server().is_secure_http_like() && origin_url_.SchemeIs(url::kHttpScheme))), delegate_(delegate), job_type_(job_type), using_ssl_(origin_url_.SchemeIs(url::kHttpsScheme) || origin_url_.SchemeIs(url::kWssScheme)), using_quic_(alternative_protocol == kProtoQUIC || (ShouldForceQuic(session, destination_, proxy_info, using_ssl_, is_websocket_)) || job_type == DNS_ALPN_H3 || job_type == PRECONNECT_DNS_ALPN_H3), quic_version_(quic_version), expect_spdy_(alternative_protocol == kProtoHTTP2 && !using_quic_), quic_request_(session_->quic_stream_factory()), pushed_stream_id_(kNoPushedStreamFound), spdy_session_key_( using_quic_ ? SpdySessionKey() : GetSpdySessionKey(proxy_info_.proxy_server(), origin_url_, request_info_.privacy_mode, request_info_.socket_tag, request_info_.network_anonymization_key, request_info_.secure_dns_policy)) { // Websocket `destination` schemes should be converted to HTTP(S). DCHECK(base::EqualsCaseInsensitiveASCII(destination_.scheme(), url::kHttpScheme) || base::EqualsCaseInsensitiveASCII(destination_.scheme(), url::kHttpsScheme)); // This class is specific to a single `ProxyServer`, so `proxy_info_` must be // non-empty. Entries beyond the first are ignored. It should simply take a // `ProxyServer`, but the full `ProxyInfo` is passed back to // `HttpNetworkTransaction`, which consumes additional fields. DCHECK(!proxy_info_.is_empty()); // QUIC can only be spoken to servers, never to proxies. if (alternative_protocol == kProtoQUIC) DCHECK(proxy_info_.is_direct()); // The Job is forced to use QUIC without a designated version, try the // preferred QUIC version that is supported by default. if (quic_version_ == quic::ParsedQuicVersion::Unsupported() && ShouldForceQuic(session, destination_, proxy_info, using_ssl_, is_websocket_)) { quic_version_ = session->context().quic_context->params()->supported_versions[0]; } if (using_quic_) { DCHECK((quic_version_ != quic::ParsedQuicVersion::Unsupported()) || (job_type_ == DNS_ALPN_H3) || (job_type_ == PRECONNECT_DNS_ALPN_H3)); } DCHECK(session); if (alternative_protocol != kProtoUnknown) { // If the alternative service protocol is specified, then the job type must // be either ALTERNATIVE or PRECONNECT. DCHECK(job_type_ == ALTERNATIVE || job_type_ == PRECONNECT); } if (expect_spdy_) { DCHECK(origin_url_.SchemeIs(url::kHttpsScheme)); } if (using_quic_) { DCHECK(session_->IsQuicEnabled()); } if (job_type_ == PRECONNECT || is_websocket_) { DCHECK(request_info_.socket_tag == SocketTag()); } if (is_websocket_) { DCHECK(origin_url_.SchemeIsWSOrWSS()); } else { DCHECK(!origin_url_.SchemeIsWSOrWSS()); } const NetLogWithSource* delegate_net_log = delegate_->GetNetLog(); if (delegate_net_log) { net_log_.BeginEvent(NetLogEventType::HTTP_STREAM_JOB, [&] { return NetLogHttpStreamJobParams( delegate_net_log->source(), request_info_.url, origin_url_, expect_spdy_, using_quic_, job_type_, priority_); }); delegate_net_log->AddEventReferencingSource( NetLogEventType::HTTP_STREAM_REQUEST_STARTED_JOB, net_log_.source()); } } HttpStreamFactory::Job::~Job() { net_log_.EndEvent(NetLogEventType::HTTP_STREAM_JOB); // When we're in a partially constructed state, waiting for the user to // provide certificate handling information or authentication, we can't reuse // this stream at all. if (next_state_ == STATE_WAITING_USER_ACTION) { connection_->socket()->Disconnect(); connection_.reset(); } // The stream could be in a partial state. It is not reusable. if (stream_.get() && next_state_ != STATE_DONE) stream_->Close(true /* not reusable */); } void HttpStreamFactory::Job::Start(HttpStreamRequest::StreamType stream_type) { stream_type_ = stream_type; StartInternal(); } int HttpStreamFactory::Job::Preconnect(int num_streams) { DCHECK_GT(num_streams, 0); HttpServerProperties* http_server_properties = session_->http_server_properties(); DCHECK(http_server_properties); // Preconnect one connection if either of the following is true: // (1) kLimitEarlyPreconnectsStreamExperiment is turned on, // HttpServerProperties is not initialized, and url scheme is cryptographic. // (2) The server supports H2 or QUIC. bool connect_one_stream = base::FeatureList::IsEnabled(kLimitEarlyPreconnectsExperiment) && !http_server_properties->IsInitialized() && request_info_.url.SchemeIsCryptographic(); if (connect_one_stream || http_server_properties->SupportsRequestPriority( url::SchemeHostPort(request_info_.url), request_info_.network_anonymization_key)) { num_streams_ = 1; } else { num_streams_ = num_streams; } return StartInternal(); } int HttpStreamFactory::Job::RestartTunnelWithProxyAuth() { DCHECK(establishing_tunnel_); DCHECK(restart_with_auth_callback_); std::move(restart_with_auth_callback_).Run(); return ERR_IO_PENDING; } LoadState HttpStreamFactory::Job::GetLoadState() const { switch (next_state_) { case STATE_INIT_CONNECTION_COMPLETE: case STATE_CREATE_STREAM_COMPLETE: return using_quic_ ? LOAD_STATE_CONNECTING : connection_->GetLoadState(); default: return LOAD_STATE_IDLE; } } void HttpStreamFactory::Job::Resume() { DCHECK_EQ(job_type_, MAIN); DCHECK_EQ(next_state_, STATE_WAIT_COMPLETE); OnIOComplete(OK); } void HttpStreamFactory::Job::Orphan() { DCHECK(job_type_ == ALTERNATIVE || job_type_ == DNS_ALPN_H3); net_log_.AddEvent(NetLogEventType::HTTP_STREAM_JOB_ORPHANED); // Watching for SPDY sessions isn't supported on orphaned jobs. // TODO(mmenke): Fix that. spdy_session_request_.reset(); } void HttpStreamFactory::Job::SetPriority(RequestPriority priority) { priority_ = priority; // Ownership of |connection_| is passed to the newly created stream // or H2 session in DoCreateStream(), and the consumer is not // notified immediately, so this call may occur when |connection_| // is null. // // Note that streams are created without a priority associated with them, // and it is up to the consumer to set their priority via // HttpStream::InitializeStream(). So there is no need for this code // to propagate priority changes to the newly created stream. if (connection_ && connection_->is_initialized()) connection_->SetPriority(priority); // TODO(akalin): Maybe Propagate this to the preconnect state. } bool HttpStreamFactory::Job::HasAvailableSpdySession() const { return !using_quic_ && CanUseExistingSpdySession() && session_->spdy_session_pool()->HasAvailableSession(spdy_session_key_, is_websocket_); } bool HttpStreamFactory::Job::HasAvailableQuicSession() const { if (!using_quic_) return false; bool require_dns_https_alpn = (job_type_ == DNS_ALPN_H3) || (job_type_ == PRECONNECT_DNS_ALPN_H3); return quic_request_.CanUseExistingSession( origin_url_, request_info_.privacy_mode, request_info_.socket_tag, request_info_.network_anonymization_key, request_info_.secure_dns_policy, require_dns_https_alpn, destination_); } bool HttpStreamFactory::Job::TargettedSocketGroupHasActiveSocket() const { DCHECK(!using_quic_); DCHECK(!is_websocket_); ClientSocketPool* pool = session_->GetSocketPool( HttpNetworkSession::NORMAL_SOCKET_POOL, proxy_info_.proxy_server()); DCHECK(pool); ClientSocketPool::GroupId connection_group( destination_, request_info_.privacy_mode, request_info_.network_anonymization_key, request_info_.secure_dns_policy); return pool->HasActiveSocket(connection_group); } bool HttpStreamFactory::Job::was_alpn_negotiated() const { return was_alpn_negotiated_; } NextProto HttpStreamFactory::Job::negotiated_protocol() const { return negotiated_protocol_; } bool HttpStreamFactory::Job::using_spdy() const { return using_spdy_; } const ProxyInfo& HttpStreamFactory::Job::proxy_info() const { return proxy_info_; } ResolveErrorInfo HttpStreamFactory::Job::resolve_error_info() const { return resolve_error_info_; } void HttpStreamFactory::Job::GetSSLInfo(SSLInfo* ssl_info) { DCHECK(using_ssl_); DCHECK(!establishing_tunnel_); DCHECK(connection_.get() && connection_->socket()); connection_->socket()->GetSSLInfo(ssl_info); } // static bool HttpStreamFactory::Job::ShouldForceQuic( HttpNetworkSession* session, const url::SchemeHostPort& destination, const ProxyInfo& proxy_info, bool using_ssl, bool is_websocket) { if (!session->IsQuicEnabled()) return false; if (is_websocket) return false; // If this is going through a QUIC proxy, only force QUIC for insecure // requests. If the request is secure, a tunnel will be needed, and those are // handled by the socket pools, using an HttpProxyConnectJob. if (proxy_info.is_quic()) return !using_ssl; const QuicParams* quic_params = session->context().quic_context->params(); // TODO(crbug.com/1206799): Consider converting `origins_to_force_quic_on` to // use url::SchemeHostPort. return (base::Contains(quic_params->origins_to_force_quic_on, HostPortPair()) || base::Contains(quic_params->origins_to_force_quic_on, HostPortPair::FromSchemeHostPort(destination))) && proxy_info.is_direct() && base::EqualsCaseInsensitiveASCII(destination.scheme(), url::kHttpsScheme); } // static SpdySessionKey HttpStreamFactory::Job::GetSpdySessionKey( const ProxyServer& proxy_server, const GURL& origin_url, PrivacyMode privacy_mode, const SocketTag& socket_tag, const NetworkAnonymizationKey& network_anonymization_key, SecureDnsPolicy secure_dns_policy) { // In the case that we're using an HTTPS proxy for an HTTP url, look for a // HTTP/2 proxy session *to* the proxy, instead of to the origin server. if (proxy_server.is_https() && origin_url.SchemeIs(url::kHttpScheme)) { return SpdySessionKey(proxy_server.host_port_pair(), ProxyServer::Direct(), PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kTrue, socket_tag, network_anonymization_key, secure_dns_policy); } return SpdySessionKey(HostPortPair::FromURL(origin_url), proxy_server, privacy_mode, SpdySessionKey::IsProxySession::kFalse, socket_tag, network_anonymization_key, secure_dns_policy); } bool HttpStreamFactory::Job::CanUseExistingSpdySession() const { DCHECK(!using_quic_); if (proxy_info_.is_direct() && session_->http_server_properties()->RequiresHTTP11( url::SchemeHostPort(request_info_.url), request_info_.network_anonymization_key)) { return false; } if (is_websocket_) return try_websocket_over_http2_; DCHECK(origin_url_.SchemeIsHTTPOrHTTPS()); // We need to make sure that if a HTTP/2 session was created for // https://somehost/ then we do not use that session for http://somehost:443/. // The only time we can use an existing session is if the request URL is // https (the normal case) or if we are connecting to a HTTP/2 proxy. // https://crbug.com/133176 return origin_url_.SchemeIs(url::kHttpsScheme) || proxy_info_.proxy_server().is_https(); } void HttpStreamFactory::Job::OnStreamReadyCallback() { DCHECK(stream_.get()); DCHECK_NE(job_type_, PRECONNECT); DCHECK_NE(job_type_, PRECONNECT_DNS_ALPN_H3); DCHECK(!is_websocket_ || try_websocket_over_http2_); MaybeCopyConnectionAttemptsFromHandle(); delegate_->OnStreamReady(this, server_ssl_config_); // |this| may be deleted after this call. } void HttpStreamFactory::Job::OnWebSocketHandshakeStreamReadyCallback() { DCHECK(websocket_stream_); DCHECK_NE(job_type_, PRECONNECT); DCHECK_NE(job_type_, PRECONNECT_DNS_ALPN_H3); DCHECK(is_websocket_); MaybeCopyConnectionAttemptsFromHandle(); delegate_->OnWebSocketHandshakeStreamReady( this, server_ssl_config_, proxy_info_, std::move(websocket_stream_)); // |this| may be deleted after this call. } void HttpStreamFactory::Job::OnBidirectionalStreamImplReadyCallback() { DCHECK(bidirectional_stream_impl_); MaybeCopyConnectionAttemptsFromHandle(); delegate_->OnBidirectionalStreamImplReady(this, server_ssl_config_, proxy_info_); // |this| may be deleted after this call. } void HttpStreamFactory::Job::OnStreamFailedCallback(int result) { DCHECK_NE(job_type_, PRECONNECT); DCHECK_NE(job_type_, PRECONNECT_DNS_ALPN_H3); MaybeCopyConnectionAttemptsFromHandle(); delegate_->OnStreamFailed(this, result, server_ssl_config_); // |this| may be deleted after this call. } void HttpStreamFactory::Job::OnCertificateErrorCallback( int result, const SSLInfo& ssl_info) { DCHECK_NE(job_type_, PRECONNECT); DCHECK_NE(job_type_, PRECONNECT_DNS_ALPN_H3); DCHECK(!spdy_session_request_); MaybeCopyConnectionAttemptsFromHandle(); delegate_->OnCertificateError(this, result, server_ssl_config_, ssl_info); // |this| may be deleted after this call. } void HttpStreamFactory::Job::OnNeedsProxyAuthCallback( const HttpResponseInfo& response, HttpAuthController* auth_controller, base::OnceClosure restart_with_auth_callback) { DCHECK_NE(job_type_, PRECONNECT); DCHECK_NE(job_type_, PRECONNECT_DNS_ALPN_H3); DCHECK(establishing_tunnel_); DCHECK(!restart_with_auth_callback_); restart_with_auth_callback_ = std::move(restart_with_auth_callback); // This is called out of band, so need to abort the SpdySessionRequest to // prevent being passed a new session while waiting on proxy auth credentials. spdy_session_request_.reset(); delegate_->OnNeedsProxyAuth(this, response, server_ssl_config_, proxy_info_, auth_controller); // |this| may be deleted after this call. } void HttpStreamFactory::Job::OnNeedsClientAuthCallback( SSLCertRequestInfo* cert_info) { DCHECK_NE(job_type_, PRECONNECT); DCHECK_NE(job_type_, PRECONNECT_DNS_ALPN_H3); DCHECK(!spdy_session_request_); delegate_->OnNeedsClientAuth(this, server_ssl_config_, cert_info); // |this| may be deleted after this call. } void HttpStreamFactory::Job::OnPreconnectsComplete(int result) { delegate_->OnPreconnectsComplete(this, result); // |this| may be deleted after this call. } void HttpStreamFactory::Job::OnIOComplete(int result) { RunLoop(result); } void HttpStreamFactory::Job::RunLoop(int result) { result = DoLoop(result); if (result == ERR_IO_PENDING) return; // Stop watching for new SpdySessions, to avoid receiving a new SPDY session // while doing anything other than waiting to establish a connection. spdy_session_request_.reset(); if ((job_type_ == PRECONNECT) || (job_type_ == PRECONNECT_DNS_ALPN_H3)) { base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(&HttpStreamFactory::Job::OnPreconnectsComplete, ptr_factory_.GetWeakPtr(), result)); return; } if (IsCertificateError(result)) { // Retrieve SSL information from the socket. SSLInfo ssl_info; GetSSLInfo(&ssl_info); next_state_ = STATE_WAITING_USER_ACTION; base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(&HttpStreamFactory::Job::OnCertificateErrorCallback, ptr_factory_.GetWeakPtr(), result, ssl_info)); return; } switch (result) { case ERR_SSL_CLIENT_AUTH_CERT_NEEDED: base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce( &Job::OnNeedsClientAuthCallback, ptr_factory_.GetWeakPtr(), base::RetainedRef(connection_->ssl_cert_request_info()))); return; case OK: next_state_ = STATE_DONE; if (is_websocket_) { DCHECK(websocket_stream_); base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(&Job::OnWebSocketHandshakeStreamReadyCallback, ptr_factory_.GetWeakPtr())); } else if (stream_type_ == HttpStreamRequest::BIDIRECTIONAL_STREAM) { if (!bidirectional_stream_impl_) { base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(&Job::OnStreamFailedCallback, ptr_factory_.GetWeakPtr(), ERR_FAILED)); } else { base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(&Job::OnBidirectionalStreamImplReadyCallback, ptr_factory_.GetWeakPtr())); } } else { DCHECK(stream_.get()); base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(&Job::OnStreamReadyCallback, ptr_factory_.GetWeakPtr())); } return; default: base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(&Job::OnStreamFailedCallback, ptr_factory_.GetWeakPtr(), result)); return; } } int HttpStreamFactory::Job::DoLoop(int result) { DCHECK_NE(next_state_, STATE_NONE); int rv = result; do { State state = next_state_; next_state_ = STATE_NONE; switch (state) { case STATE_START: DCHECK_EQ(OK, rv); rv = DoStart(); break; case STATE_WAIT: DCHECK_EQ(OK, rv); rv = DoWait(); break; case STATE_WAIT_COMPLETE: rv = DoWaitComplete(rv); break; case STATE_INIT_CONNECTION: DCHECK_EQ(OK, rv); rv = DoInitConnection(); break; case STATE_INIT_CONNECTION_COMPLETE: rv = DoInitConnectionComplete(rv); break; case STATE_WAITING_USER_ACTION: rv = DoWaitingUserAction(rv); break; case STATE_CREATE_STREAM: DCHECK_EQ(OK, rv); rv = DoCreateStream(); break; case STATE_CREATE_STREAM_COMPLETE: rv = DoCreateStreamComplete(rv); break; default: NOTREACHED() << "bad state"; rv = ERR_FAILED; break; } } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE); return rv; } int HttpStreamFactory::Job::StartInternal() { CHECK_EQ(STATE_NONE, next_state_); next_state_ = STATE_START; RunLoop(OK); return ERR_IO_PENDING; } int HttpStreamFactory::Job::DoStart() { // Don't connect to restricted ports. if (!IsPortAllowedForScheme(destination_.port(), request_info_.url.scheme_piece())) { return ERR_UNSAFE_PORT; } if (!session_->params().enable_quic_proxies_for_https_urls && proxy_info_.is_quic() && request_info_.url.SchemeIsCryptographic()) { return ERR_NOT_IMPLEMENTED; } next_state_ = STATE_WAIT; return OK; } int HttpStreamFactory::Job::DoWait() { next_state_ = STATE_WAIT_COMPLETE; bool should_wait = delegate_->ShouldWait(this); net_log_.AddEntryWithBoolParams(NetLogEventType::HTTP_STREAM_JOB_WAITING, NetLogEventPhase::BEGIN, "should_wait", should_wait); if (should_wait) return ERR_IO_PENDING; return OK; } int HttpStreamFactory::Job::DoWaitComplete(int result) { net_log_.EndEvent(NetLogEventType::HTTP_STREAM_JOB_WAITING); DCHECK_EQ(OK, result); next_state_ = STATE_INIT_CONNECTION; return OK; } void HttpStreamFactory::Job::ResumeInitConnection() { if (init_connection_already_resumed_) return; DCHECK_EQ(next_state_, STATE_INIT_CONNECTION); net_log_.AddEvent(NetLogEventType::HTTP_STREAM_JOB_RESUME_INIT_CONNECTION); init_connection_already_resumed_ = true; OnIOComplete(OK); } int HttpStreamFactory::Job::DoInitConnection() { net_log_.BeginEvent(NetLogEventType::HTTP_STREAM_JOB_INIT_CONNECTION); int result = DoInitConnectionImpl(); if (!expect_on_quic_session_created_ && !expect_on_quic_host_resolution_) { delegate_->OnConnectionInitialized(this, result); } return result; } int HttpStreamFactory::Job::DoInitConnectionImpl() { DCHECK(!connection_->is_initialized()); if (using_quic_ && !proxy_info_.is_quic() && !proxy_info_.is_direct()) { // QUIC can not be spoken to non-QUIC proxies. This error should not be // user visible, because the non-alternative Job should be resumed. return ERR_NO_SUPPORTED_PROXIES; } DCHECK(proxy_info_.proxy_server().is_valid()); next_state_ = STATE_INIT_CONNECTION_COMPLETE; if (proxy_info_.is_secure_http_like()) { // Disable network fetches for HTTPS proxies, since the network requests // are probably going to need to go through the proxy too. proxy_ssl_config_.disable_cert_verification_network_fetches = true; } if (using_ssl_) { // Prior to HTTP/2 and SPDY, some servers use TLS renegotiation to request // TLS client authentication after the HTTP request was sent. Allow // renegotiation for only those connections. // // Note that this does NOT implement the provision in // https://http2.github.io/http2-spec/#rfc.section.9.2.1 which allows the // server to request a renegotiation immediately before sending the // connection preface as waiting for the preface would cost the round trip // that False Start otherwise saves. server_ssl_config_.renego_allowed_default = true; server_ssl_config_.renego_allowed_for_protos.push_back(kProtoHTTP11); } server_ssl_config_.alpn_protos = session_->GetAlpnProtos(); proxy_ssl_config_.alpn_protos = session_->GetAlpnProtos(); server_ssl_config_.application_settings = session_->GetApplicationSettings(); proxy_ssl_config_.application_settings = session_->GetApplicationSettings(); server_ssl_config_.ignore_certificate_errors = session_->params().ignore_certificate_errors; proxy_ssl_config_.ignore_certificate_errors = session_->params().ignore_certificate_errors; // TODO(https://crbug.com/964642): Also enable 0-RTT for TLS proxies. server_ssl_config_.early_data_enabled = session_->params().enable_early_data; if (using_quic_) return DoInitConnectionImplQuic(); // Check first if there is a pushed stream matching the request, or an HTTP/2 // connection this request can pool to. If so, then go straight to using // that. if (CanUseExistingSpdySession()) { if (!is_websocket_) { session_->spdy_session_pool()->push_promise_index()->ClaimPushedStream( spdy_session_key_, origin_url_, request_info_, &existing_spdy_session_, &pushed_stream_id_); } if (!existing_spdy_session_) { if (!spdy_session_request_) { // If not currently watching for an H2 session, use // SpdySessionPool::RequestSession() to check for a session, and start // watching for one. bool should_throttle_connect = ShouldThrottleConnectForSpdy(); base::RepeatingClosure resume_callback = should_throttle_connect ? base::BindRepeating( &HttpStreamFactory::Job::ResumeInitConnection, ptr_factory_.GetWeakPtr()) : base::RepeatingClosure(); bool is_blocking_request_for_session; existing_spdy_session_ = session_->spdy_session_pool()->RequestSession( spdy_session_key_, enable_ip_based_pooling_, is_websocket_, net_log_, resume_callback, this, &spdy_session_request_, &is_blocking_request_for_session); if (!existing_spdy_session_ && should_throttle_connect && !is_blocking_request_for_session) { net_log_.AddEvent(NetLogEventType::HTTP_STREAM_JOB_THROTTLED); next_state_ = STATE_INIT_CONNECTION; base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( FROM_HERE, resume_callback, base::Milliseconds(kHTTP2ThrottleMs)); return ERR_IO_PENDING; } } else if (enable_ip_based_pooling_) { // If already watching for an H2 session, still need to check for an // existing connection that can be reused through IP pooling, as those // don't post session available notifications. // // TODO(mmenke): Make sessions created through IP pooling invoke the // callback. existing_spdy_session_ = session_->spdy_session_pool()->FindAvailableSession( spdy_session_key_, enable_ip_based_pooling_, is_websocket_, net_log_); } } if (existing_spdy_session_) { // Stop watching for SpdySessions. spdy_session_request_.reset(); // If we're preconnecting, but we already have a SpdySession, we don't // actually need to preconnect any sockets, so we're done. if (job_type_ == PRECONNECT) return OK; using_spdy_ = true; next_state_ = STATE_CREATE_STREAM; return OK; } } if (proxy_info_.is_http_like()) establishing_tunnel_ = using_ssl_; HttpServerProperties* http_server_properties = session_->http_server_properties(); if (http_server_properties) { http_server_properties->MaybeForceHTTP11( url::SchemeHostPort(request_info_.url), request_info_.network_anonymization_key, &server_ssl_config_); if (proxy_info_.is_https()) { http_server_properties->MaybeForceHTTP11( url::SchemeHostPort( url::kHttpsScheme, proxy_info_.proxy_server().host_port_pair().host(), proxy_info_.proxy_server().host_port_pair().port()), request_info_.network_anonymization_key, &proxy_ssl_config_); } } if (job_type_ == PRECONNECT) { DCHECK(!is_websocket_); DCHECK(request_info_.socket_tag == SocketTag()); // The lifeime of the preconnect tasks is not controlled by |connection_|. // It may outlives |this|. So we can't use |io_callback_| which holds // base::Unretained(this). auto callback = base::BindOnce(&Job::OnIOComplete, ptr_factory_.GetWeakPtr()); return PreconnectSocketsForHttpRequest( destination_, request_info_.load_flags, priority_, session_, proxy_info_, server_ssl_config_, proxy_ssl_config_, request_info_.privacy_mode, request_info_.network_anonymization_key, request_info_.secure_dns_policy, net_log_, num_streams_, std::move(callback)); } ClientSocketPool::ProxyAuthCallback proxy_auth_callback = base::BindRepeating(&HttpStreamFactory::Job::OnNeedsProxyAuthCallback, base::Unretained(this)); if (is_websocket_) { DCHECK(request_info_.socket_tag == SocketTag()); DCHECK_EQ(SecureDnsPolicy::kAllow, request_info_.secure_dns_policy); // Only offer HTTP/1.1 for WebSockets. Although RFC 8441 defines WebSockets // over HTTP/2, a single WSS/HTTPS origin may support HTTP over HTTP/2 // without supporting WebSockets over HTTP/2. Offering HTTP/2 for a fresh // connection would break such origins. // // However, still offer HTTP/1.1 rather than skipping ALPN entirely. While // this will not change the application protocol (HTTP/1.1 is default), it // provides hardens against cross-protocol attacks and allows for the False // Start (RFC 7918) optimization. SSLConfig websocket_server_ssl_config = server_ssl_config_; websocket_server_ssl_config.alpn_protos = {kProtoHTTP11}; return InitSocketHandleForWebSocketRequest( destination_, request_info_.load_flags, priority_, session_, proxy_info_, websocket_server_ssl_config, proxy_ssl_config_, request_info_.privacy_mode, request_info_.network_anonymization_key, net_log_, connection_.get(), io_callback_, proxy_auth_callback); } return InitSocketHandleForHttpRequest( destination_, request_info_.load_flags, priority_, session_, proxy_info_, server_ssl_config_, proxy_ssl_config_, request_info_.privacy_mode, request_info_.network_anonymization_key, request_info_.secure_dns_policy, request_info_.socket_tag, net_log_, connection_.get(), io_callback_, proxy_auth_callback); } int HttpStreamFactory::Job::DoInitConnectionImplQuic() { url::SchemeHostPort destination; SSLConfig* ssl_config; GURL url(request_info_.url); if (proxy_info_.is_quic()) { ssl_config = &proxy_ssl_config_; const HostPortPair& proxy_endpoint = proxy_info_.proxy_server().host_port_pair(); destination = url::SchemeHostPort(url::kHttpsScheme, proxy_endpoint.host(), proxy_endpoint.port()); url = destination.GetURL(); } else { DCHECK(using_ssl_); destination = destination_; ssl_config = &server_ssl_config_; } DCHECK(url.SchemeIs(url::kHttpsScheme)); bool require_dns_https_alpn = (job_type_ == DNS_ALPN_H3) || (job_type_ == PRECONNECT_DNS_ALPN_H3); int rv = quic_request_.Request( std::move(destination), quic_version_, request_info_.privacy_mode, priority_, request_info_.socket_tag, request_info_.network_anonymization_key, request_info_.secure_dns_policy, proxy_info_.is_direct(), require_dns_https_alpn, ssl_config->GetCertVerifyFlags(), url, net_log_, &net_error_details_, base::BindOnce(&Job::OnFailedOnDefaultNetwork, ptr_factory_.GetWeakPtr()), io_callback_); if (rv == OK) { using_existing_quic_session_ = true; } else if (rv == ERR_IO_PENDING) { // There's no available QUIC session. Inform the delegate how long to // delay the main job. delegate_->MaybeSetWaitTimeForMainJob( quic_request_.GetTimeDelayForWaitingJob()); expect_on_quic_host_resolution_ = quic_request_.WaitForHostResolution( base::BindOnce(&Job::OnQuicHostResolution, base::Unretained(this))); expect_on_quic_session_created_ = quic_request_.WaitForQuicSessionCreation( base::BindOnce(&Job::OnQuicSessionCreated, ptr_factory_.GetWeakPtr())); } return rv; } void HttpStreamFactory::Job::OnQuicSessionCreated(int result) { DCHECK(expect_on_quic_session_created_); expect_on_quic_session_created_ = false; delegate_->OnConnectionInitialized(this, result); } void HttpStreamFactory::Job::OnQuicHostResolution(int result) { DCHECK(expect_on_quic_host_resolution_); expect_on_quic_host_resolution_ = false; if (!expect_on_quic_session_created_) { delegate_->OnConnectionInitialized(this, result); } } void HttpStreamFactory::Job::OnFailedOnDefaultNetwork(int result) { DCHECK(job_type_ == ALTERNATIVE || job_type_ == DNS_ALPN_H3); DCHECK(using_quic_); delegate_->OnFailedOnDefaultNetwork(this); } int HttpStreamFactory::Job::DoInitConnectionComplete(int result) { net_log_.EndEvent(NetLogEventType::HTTP_STREAM_JOB_INIT_CONNECTION); // No need to continue waiting for a session, once a connection is // established. spdy_session_request_.reset(); if ((job_type_ == PRECONNECT) || (job_type_ == PRECONNECT_DNS_ALPN_H3)) { if (using_quic_) return result; DCHECK_EQ(OK, result); return OK; } resolve_error_info_ = connection_->resolve_error_info(); // Determine the protocol (HTTP/1.1, HTTP/2, or HTTP/3). This covers both the // origin and some proxy cases. First, if the URL is HTTPS (or WSS), we may // negotiate HTTP/2 or HTTP/3 with the origin. Second, non-tunneled requests // (i.e. HTTP URLs) through an HTTPS or QUIC proxy work by sending the request // to the proxy directly. In that case, this logic also handles the proxy's // negotiated protocol. HTTPS requests are always tunneled, so at most one of // these applies. // // Tunneled requests may also negotiate ALPN at the proxy, but // HttpProxyConnectJob handles ALPN. The resulting StreamSocket will not // report an ALPN protocol. if (result == OK) { if (using_quic_) { // TODO(davidben): Record these values consistently between QUIC and TCP // below. In the QUIC case, we only record it for origin connections. In // the TCP case, we also record it for non-tunneled, proxied requests. if (using_ssl_) { was_alpn_negotiated_ = true; negotiated_protocol_ = kProtoQUIC; } } else if (connection_->socket()->WasAlpnNegotiated()) { // Only connections that use TLS can negotiate ALPN. DCHECK(using_ssl_ || proxy_info_.is_secure_http_like()); was_alpn_negotiated_ = true; negotiated_protocol_ = connection_->socket()->GetNegotiatedProtocol(); net_log_.AddEvent(NetLogEventType::HTTP_STREAM_REQUEST_PROTO, [&] { return NetLogHttpStreamProtoParams(negotiated_protocol_); }); if (negotiated_protocol_ == kProtoHTTP2) { if (is_websocket_) { // WebSocket is not supported over a fresh HTTP/2 connection. This // should not be reachable. For the origin, we do not request HTTP/2 // on fresh WebSockets connections, because not all HTTP/2 servers // implement RFC 8441. For proxies, WebSockets are always tunneled. // // TODO(davidben): This isn't a CHECK() because, previously, it was // reachable in https://crbug.com/828865. However, if reachable, it // means a bug in the socket pools. The socket pools have since been // cleaned up, so this may no longer be reachable. Restore the CHECK // and see if this is still needed. return ERR_NOT_IMPLEMENTED; } using_spdy_ = true; } } } if (proxy_info_.is_quic() && using_quic_ && result < 0) return ReconsiderProxyAfterError(result); if (expect_spdy_ && !using_spdy_) return ERR_ALPN_NEGOTIATION_FAILED; // |result| may be the result of any of the stacked protocols. The following // logic is used when determining how to interpret an error. // If |result| < 0: // and connection_->socket() != NULL, then the SSL handshake ran and it // is a potentially recoverable error. // and connection_->socket == NULL and connection_->is_ssl_error() is true, // then the SSL handshake ran with an unrecoverable error. // otherwise, the error came from one of the other protocols. bool ssl_started = using_ssl_ && (result == OK || connection_->socket() || connection_->is_ssl_error()); if (!ssl_started && result < 0 && (expect_spdy_ || using_quic_)) return result; if (using_quic_) { if (result < 0) return result; if (stream_type_ == HttpStreamRequest::BIDIRECTIONAL_STREAM) { std::unique_ptr session = quic_request_.ReleaseSessionHandle(); if (!session) { // Quic session is closed before stream can be created. return ERR_CONNECTION_CLOSED; } bidirectional_stream_impl_ = std::make_unique(std::move(session)); } else { std::unique_ptr session = quic_request_.ReleaseSessionHandle(); if (!session) { // Quic session is closed before stream can be created. return ERR_CONNECTION_CLOSED; } auto dns_aliases = session->GetDnsAliasesForSessionKey(quic_request_.session_key()); stream_ = std::make_unique(std::move(session), std::move(dns_aliases)); } next_state_ = STATE_NONE; return OK; } if (result < 0 && !ssl_started) return ReconsiderProxyAfterError(result); establishing_tunnel_ = false; // Handle SSL errors below. if (using_ssl_) { DCHECK(ssl_started); if (IsCertificateError(result)) { SSLInfo ssl_info; GetSSLInfo(&ssl_info); if (ssl_info.cert) { // Add the bad certificate to the set of allowed certificates in the // SSL config object. This data structure will be consulted after // calling RestartIgnoringLastError(). And the user will be asked // interactively before RestartIgnoringLastError() is ever called. server_ssl_config_.allowed_bad_certs.emplace_back(ssl_info.cert, ssl_info.cert_status); } } if (result < 0) return result; } next_state_ = STATE_CREATE_STREAM; return OK; } int HttpStreamFactory::Job::DoWaitingUserAction(int result) { // This state indicates that the stream request is in a partially // completed state, and we've called back to the delegate for more // information. // We're always waiting here for the delegate to call us back. return ERR_IO_PENDING; } int HttpStreamFactory::Job::SetSpdyHttpStreamOrBidirectionalStreamImpl( base::WeakPtr session) { DCHECK(using_spdy_); auto dns_aliases = session_->spdy_session_pool()->GetDnsAliasesForSessionKey( spdy_session_key_); if (is_websocket_) { DCHECK_NE(job_type_, PRECONNECT); DCHECK_NE(job_type_, PRECONNECT_DNS_ALPN_H3); DCHECK(delegate_->websocket_handshake_stream_create_helper()); if (!try_websocket_over_http2_) { // TODO(davidben): Is this reachable? We shouldn't receive a SpdySession // if not requested. return ERR_NOT_IMPLEMENTED; } websocket_stream_ = delegate_->websocket_handshake_stream_create_helper() ->CreateHttp2Stream(session, std::move(dns_aliases)); return OK; } if (stream_type_ == HttpStreamRequest::BIDIRECTIONAL_STREAM) { bidirectional_stream_impl_ = std::make_unique( session, net_log_.source()); return OK; } // TODO(willchan): Delete this code, because eventually, the HttpStreamFactory // will be creating all the SpdyHttpStreams, since it will know when // SpdySessions become available. stream_ = std::make_unique( session, pushed_stream_id_, net_log_.source(), std::move(dns_aliases)); return OK; } int HttpStreamFactory::Job::DoCreateStream() { DCHECK(connection_->socket() || existing_spdy_session_.get()); DCHECK(!using_quic_); next_state_ = STATE_CREATE_STREAM_COMPLETE; if (!using_spdy_) { DCHECK(!expect_spdy_); bool using_proxy = (proxy_info_.is_http_like()) && request_info_.url.SchemeIs(url::kHttpScheme); if (is_websocket_) { DCHECK_NE(job_type_, PRECONNECT); DCHECK_NE(job_type_, PRECONNECT_DNS_ALPN_H3); DCHECK(delegate_->websocket_handshake_stream_create_helper()); websocket_stream_ = delegate_->websocket_handshake_stream_create_helper() ->CreateBasicStream(std::move(connection_), using_proxy, session_->websocket_endpoint_lock_manager()); } else { if (request_info_.upload_data_stream && !request_info_.upload_data_stream->AllowHTTP1()) { return ERR_H2_OR_QUIC_REQUIRED; } stream_ = std::make_unique(std::move(connection_), using_proxy); } return OK; } CHECK(!stream_.get()); // It is possible that a pushed stream has been opened by a server since last // time Job checked above. if (!existing_spdy_session_) { // WebSocket over HTTP/2 is only allowed to use existing HTTP/2 connections. // Therefore |using_spdy_| could not have been set unless a connection had // already been found. DCHECK(!is_websocket_); session_->spdy_session_pool()->push_promise_index()->ClaimPushedStream( spdy_session_key_, origin_url_, request_info_, &existing_spdy_session_, &pushed_stream_id_); // It is also possible that an HTTP/2 connection has been established since // last time Job checked above. if (!existing_spdy_session_) { existing_spdy_session_ = session_->spdy_session_pool()->FindAvailableSession( spdy_session_key_, enable_ip_based_pooling_, /* is_websocket = */ false, net_log_); } } if (existing_spdy_session_) { // We picked up an existing session, so we don't need our socket. if (connection_->socket()) connection_->socket()->Disconnect(); connection_->Reset(); int set_result = SetSpdyHttpStreamOrBidirectionalStreamImpl(existing_spdy_session_); existing_spdy_session_.reset(); return set_result; } // Close idle sockets in this group, since subsequent requests will go over // |spdy_session|. if (connection_->socket()->IsConnected()) connection_->CloseIdleSocketsInGroup("Switching to HTTP2 session"); base::WeakPtr spdy_session; int rv = session_->spdy_session_pool()->CreateAvailableSessionFromSocketHandle( spdy_session_key_, std::move(connection_), net_log_, &spdy_session); if (rv != OK) { return rv; } url::SchemeHostPort scheme_host_port( using_ssl_ ? url::kHttpsScheme : url::kHttpScheme, spdy_session_key_.host_port_pair().host(), spdy_session_key_.host_port_pair().port()); HttpServerProperties* http_server_properties = session_->http_server_properties(); if (http_server_properties) { http_server_properties->SetSupportsSpdy( scheme_host_port, request_info_.network_anonymization_key, true /* supports_spdy */); } // Create a SpdyHttpStream or a BidirectionalStreamImpl attached to the // session. return SetSpdyHttpStreamOrBidirectionalStreamImpl(spdy_session); } int HttpStreamFactory::Job::DoCreateStreamComplete(int result) { if (result < 0) return result; session_->proxy_resolution_service()->ReportSuccess(proxy_info_); next_state_ = STATE_NONE; return OK; } void HttpStreamFactory::Job::OnSpdySessionAvailable( base::WeakPtr spdy_session) { DCHECK(spdy_session); // No need for the connection any more, since |spdy_session| can be used // instead, and there's no benefit from keeping the old ConnectJob in the // socket pool. if (connection_) connection_->ResetAndCloseSocket(); // Once a connection is initialized, or if there's any out-of-band callback, // like proxy auth challenge, the SpdySessionRequest is cancelled. DCHECK(next_state_ == STATE_INIT_CONNECTION || next_state_ == STATE_INIT_CONNECTION_COMPLETE); // Ignore calls to ResumeInitConnection() from either the timer or the // SpdySessionPool. init_connection_already_resumed_ = true; // If this is a preconnect, nothing left do to. if (job_type_ == PRECONNECT) { OnPreconnectsComplete(OK); return; } using_spdy_ = true; existing_spdy_session_ = spdy_session; next_state_ = STATE_CREATE_STREAM; // This will synchronously close |connection_|, so no need to worry about it // calling back into |this|. RunLoop(net::OK); } int HttpStreamFactory::Job::ReconsiderProxyAfterError(int error) { // Check if the error was a proxy failure. if (!CanFalloverToNextProxy(proxy_info_.proxy_server(), error, &error)) return error; should_reconsider_proxy_ = true; return error; } void HttpStreamFactory::Job::MaybeCopyConnectionAttemptsFromHandle() { if (!connection_) return; delegate_->AddConnectionAttemptsToRequest(this, connection_->connection_attempts()); } HttpStreamFactory::JobFactory::JobFactory() = default; HttpStreamFactory::JobFactory::~JobFactory() = default; std::unique_ptr HttpStreamFactory::JobFactory::CreateJob( HttpStreamFactory::Job::Delegate* delegate, HttpStreamFactory::JobType job_type, HttpNetworkSession* session, const HttpRequestInfo& request_info, RequestPriority priority, const ProxyInfo& proxy_info, const SSLConfig& server_ssl_config, const SSLConfig& proxy_ssl_config, url::SchemeHostPort destination, GURL origin_url, bool is_websocket, bool enable_ip_based_pooling, NetLog* net_log, NextProto alternative_protocol, quic::ParsedQuicVersion quic_version) { return std::make_unique( delegate, job_type, session, request_info, priority, proxy_info, server_ssl_config, proxy_ssl_config, std::move(destination), origin_url, alternative_protocol, quic_version, is_websocket, enable_ip_based_pooling, net_log); } bool HttpStreamFactory::Job::ShouldThrottleConnectForSpdy() const { DCHECK(!using_quic_); DCHECK(!spdy_session_request_); // If the job has previously been throttled, don't throttle it again. if (init_connection_already_resumed_) return false; url::SchemeHostPort scheme_host_port( using_ssl_ ? url::kHttpsScheme : url::kHttpScheme, spdy_session_key_.host_port_pair().host(), spdy_session_key_.host_port_pair().port()); // Only throttle the request if the server is believed to support H2. return session_->http_server_properties()->GetSupportsSpdy( scheme_host_port, request_info_.network_anonymization_key); } } // namespace net