| // Copyright 2016 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_controller.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "base/containers/contains.h" |
| #include "base/functional/bind.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/trace_event/memory_usage_estimator.h" |
| #include "base/values.h" |
| #include "net/base/features.h" |
| #include "net/base/host_mapping_rules.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/privacy_mode.h" |
| #include "net/base/proxy_server.h" |
| #include "net/base/proxy_string_util.h" |
| #include "net/base/url_util.h" |
| #include "net/http/bidirectional_stream_impl.h" |
| #include "net/http/transport_security_state.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_with_source.h" |
| #include "net/proxy_resolution/proxy_resolution_request.h" |
| #include "net/spdy/spdy_session.h" |
| #include "url/gurl.h" |
| #include "url/scheme_host_port.h" |
| #include "url/third_party/mozilla/url_parse.h" |
| #include "url/url_canon.h" |
| #include "url/url_constants.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| #if defined(STARBOARD) |
| const int kDefaultQUICServerPort = 443; |
| #endif |
| |
| // Returns parameters associated with the proxy resolution. |
| base::Value::Dict NetLogHttpStreamJobProxyServerResolved( |
| const ProxyServer& proxy_server) { |
| base::Value::Dict dict; |
| |
| dict.Set("proxy_server", proxy_server.is_valid() |
| ? ProxyServerToPacResultElement(proxy_server) |
| : std::string()); |
| return dict; |
| } |
| |
| GURL CreateAltSvcUrl(const GURL& origin_url, |
| const HostPortPair& alternative_destination) { |
| DCHECK(origin_url.is_valid()); |
| DCHECK(origin_url.IsStandard()); |
| |
| GURL::Replacements replacements; |
| std::string port_str = base::NumberToString(alternative_destination.port()); |
| replacements.SetPortStr(port_str); |
| replacements.SetHostStr(alternative_destination.host()); |
| |
| return origin_url.ReplaceComponents(replacements); |
| } |
| |
| void ConvertWsToHttp(url::SchemeHostPort& input) { |
| if (base::EqualsCaseInsensitiveASCII(input.scheme(), url::kHttpScheme) || |
| base::EqualsCaseInsensitiveASCII(input.scheme(), url::kHttpsScheme)) { |
| return; |
| } |
| |
| if (base::EqualsCaseInsensitiveASCII(input.scheme(), url::kWsScheme)) { |
| input = url::SchemeHostPort(url::kHttpScheme, input.host(), input.port()); |
| return; |
| } |
| |
| DCHECK(base::EqualsCaseInsensitiveASCII(input.scheme(), url::kWssScheme)); |
| input = url::SchemeHostPort(url::kHttpsScheme, input.host(), input.port()); |
| } |
| |
| void HistogramProxyUsed(const ProxyInfo& proxy_info, bool success) { |
| const ProxyServer::Scheme max_scheme = ProxyServer::Scheme::SCHEME_QUIC; |
| ProxyServer::Scheme proxy_scheme = ProxyServer::Scheme::SCHEME_DIRECT; |
| if (!proxy_info.is_empty()) |
| proxy_scheme = proxy_info.proxy_server().scheme(); |
| if (success) { |
| UMA_HISTOGRAM_ENUMERATION("Net.HttpJob.ProxyTypeSuccess", proxy_scheme, |
| max_scheme); |
| } else { |
| UMA_HISTOGRAM_ENUMERATION("Net.HttpJob.ProxyTypeFailed", proxy_scheme, |
| max_scheme); |
| } |
| } |
| |
| // Generate a AlternativeService for DNS alt job. Note: Chrome does not yet |
| // support different port DNS alpn. |
| AlternativeService GetAlternativeServiceForDnsJob(const GURL& url) { |
| return AlternativeService(kProtoQUIC, HostPortPair::FromURL(url)); |
| } |
| |
| } // namespace |
| |
| // The maximum time to wait for the alternate job to complete before resuming |
| // the main job. |
| const int kMaxDelayTimeForMainJobSecs = 3; |
| |
| base::Value::Dict NetLogJobControllerParams(const HttpRequestInfo& request_info, |
| bool is_preconnect) { |
| base::Value::Dict dict; |
| dict.Set("url", request_info.url.possibly_invalid_spec()); |
| dict.Set("is_preconnect", is_preconnect); |
| dict.Set("privacy_mode", PrivacyModeToDebugString(request_info.privacy_mode)); |
| |
| return dict; |
| } |
| |
| base::Value::Dict NetLogAltSvcParams(const AlternativeServiceInfo* alt_svc_info, |
| bool is_broken) { |
| base::Value::Dict dict; |
| dict.Set("alt_svc", alt_svc_info->ToString()); |
| dict.Set("is_broken", is_broken); |
| return dict; |
| } |
| |
| HttpStreamFactory::JobController::JobController( |
| HttpStreamFactory* factory, |
| HttpStreamRequest::Delegate* delegate, |
| HttpNetworkSession* session, |
| JobFactory* job_factory, |
| const HttpRequestInfo& request_info, |
| bool is_preconnect, |
| bool is_websocket, |
| bool enable_ip_based_pooling, |
| bool enable_alternative_services, |
| bool delay_main_job_with_available_spdy_session, |
| const SSLConfig& server_ssl_config, |
| const SSLConfig& proxy_ssl_config) |
| : factory_(factory), |
| session_(session), |
| job_factory_(job_factory), |
| delegate_(delegate), |
| is_preconnect_(is_preconnect), |
| is_websocket_(is_websocket), |
| enable_ip_based_pooling_(enable_ip_based_pooling), |
| enable_alternative_services_(enable_alternative_services), |
| delay_main_job_with_available_spdy_session_( |
| delay_main_job_with_available_spdy_session), |
| request_info_(request_info), |
| server_ssl_config_(server_ssl_config), |
| proxy_ssl_config_(proxy_ssl_config), |
| net_log_(NetLogWithSource::Make( |
| session->net_log(), |
| NetLogSourceType::HTTP_STREAM_JOB_CONTROLLER)) { |
| DCHECK(factory); |
| DCHECK(base::EqualsCaseInsensitiveASCII(request_info_.url.scheme_piece(), |
| url::kHttpScheme) || |
| base::EqualsCaseInsensitiveASCII(request_info_.url.scheme_piece(), |
| url::kHttpsScheme) || |
| base::EqualsCaseInsensitiveASCII(request_info_.url.scheme_piece(), |
| url::kWsScheme) || |
| base::EqualsCaseInsensitiveASCII(request_info_.url.scheme_piece(), |
| url::kWssScheme)); |
| // Preconnects do not require a NetworkIsolationKey so we don't require it to |
| // be set consistently with the NetworkAnonymizationKey here. |
| if (!is_preconnect) { |
| DCHECK(request_info.IsConsistent()); |
| } |
| |
| net_log_.BeginEvent(NetLogEventType::HTTP_STREAM_JOB_CONTROLLER, [&] { |
| return NetLogJobControllerParams(request_info, is_preconnect); |
| }); |
| } |
| |
| HttpStreamFactory::JobController::~JobController() { |
| main_job_.reset(); |
| alternative_job_.reset(); |
| dns_alpn_h3_job_.reset(); |
| bound_job_ = nullptr; |
| if (proxy_resolve_request_) { |
| DCHECK_EQ(STATE_RESOLVE_PROXY_COMPLETE, next_state_); |
| proxy_resolve_request_.reset(); |
| } |
| net_log_.EndEvent(NetLogEventType::HTTP_STREAM_JOB_CONTROLLER); |
| } |
| |
| std::unique_ptr<HttpStreamRequest> HttpStreamFactory::JobController::Start( |
| HttpStreamRequest::Delegate* delegate, |
| WebSocketHandshakeStreamBase::CreateHelper* |
| websocket_handshake_stream_create_helper, |
| const NetLogWithSource& source_net_log, |
| HttpStreamRequest::StreamType stream_type, |
| RequestPriority priority) { |
| DCHECK(factory_); |
| DCHECK(!request_); |
| |
| stream_type_ = stream_type; |
| priority_ = priority; |
| |
| auto request = std::make_unique<HttpStreamRequest>( |
| request_info_.url, this, delegate, |
| websocket_handshake_stream_create_helper, source_net_log, stream_type); |
| // Keep a raw pointer but release ownership of HttpStreamRequest instance. |
| request_ = request.get(); |
| |
| // Associates |net_log_| with |source_net_log|. |
| source_net_log.AddEventReferencingSource( |
| NetLogEventType::HTTP_STREAM_JOB_CONTROLLER_BOUND, net_log_.source()); |
| net_log_.AddEventReferencingSource( |
| NetLogEventType::HTTP_STREAM_JOB_CONTROLLER_BOUND, |
| source_net_log.source()); |
| |
| RunLoop(OK); |
| return request; |
| } |
| |
| void HttpStreamFactory::JobController::Preconnect(int num_streams) { |
| DCHECK(!main_job_); |
| DCHECK(!alternative_job_); |
| DCHECK(is_preconnect_); |
| |
| stream_type_ = HttpStreamRequest::HTTP_STREAM; |
| num_streams_ = num_streams; |
| |
| RunLoop(OK); |
| } |
| |
| LoadState HttpStreamFactory::JobController::GetLoadState() const { |
| DCHECK(request_); |
| if (next_state_ == STATE_RESOLVE_PROXY_COMPLETE) |
| return proxy_resolve_request_->GetLoadState(); |
| if (bound_job_) |
| return bound_job_->GetLoadState(); |
| if (main_job_) |
| return main_job_->GetLoadState(); |
| if (alternative_job_) |
| return alternative_job_->GetLoadState(); |
| if (dns_alpn_h3_job_) |
| return dns_alpn_h3_job_->GetLoadState(); |
| |
| // When proxy resolution fails, there is no job created and |
| // NotifyRequestFailed() is executed one message loop iteration later. |
| return LOAD_STATE_IDLE; |
| } |
| |
| void HttpStreamFactory::JobController::OnRequestComplete() { |
| DCHECK(request_); |
| request_ = nullptr; |
| |
| if (!job_bound_) { |
| alternative_job_.reset(); |
| main_job_.reset(); |
| dns_alpn_h3_job_.reset(); |
| } else { |
| if (bound_job_->job_type() == MAIN) { |
| bound_job_ = nullptr; |
| main_job_.reset(); |
| } else if (bound_job_->job_type() == ALTERNATIVE) { |
| bound_job_ = nullptr; |
| alternative_job_.reset(); |
| } else { |
| DCHECK(bound_job_->job_type() == DNS_ALPN_H3); |
| bound_job_ = nullptr; |
| dns_alpn_h3_job_.reset(); |
| } |
| } |
| MaybeNotifyFactoryOfCompletion(); |
| } |
| |
| int HttpStreamFactory::JobController::RestartTunnelWithProxyAuth() { |
| DCHECK(bound_job_); |
| return bound_job_->RestartTunnelWithProxyAuth(); |
| } |
| |
| void HttpStreamFactory::JobController::SetPriority(RequestPriority priority) { |
| if (main_job_) { |
| main_job_->SetPriority(priority); |
| } |
| if (alternative_job_) { |
| alternative_job_->SetPriority(priority); |
| } |
| if (dns_alpn_h3_job_) { |
| dns_alpn_h3_job_->SetPriority(priority); |
| } |
| if (preconnect_backup_job_) { |
| preconnect_backup_job_->SetPriority(priority); |
| } |
| } |
| |
| void HttpStreamFactory::JobController::OnStreamReady( |
| Job* job, |
| const SSLConfig& used_ssl_config) { |
| DCHECK(job); |
| |
| if (IsJobOrphaned(job)) { |
| // We have bound a job to the associated HttpStreamRequest, |job| has been |
| // orphaned. |
| OnOrphanedJobComplete(job); |
| return; |
| } |
| std::unique_ptr<HttpStream> stream = job->ReleaseStream(); |
| DCHECK(stream); |
| |
| MarkRequestComplete(job); |
| |
| if (!request_) |
| return; |
| DCHECK(!is_websocket_); |
| DCHECK_EQ(HttpStreamRequest::HTTP_STREAM, request_->stream_type()); |
| OnJobSucceeded(job); |
| |
| // TODO(bnc): Remove when https://crbug.com/461981 is fixed. |
| CHECK(request_); |
| |
| DCHECK(request_->completed()); |
| |
| HistogramProxyUsed(job->proxy_info(), /*success=*/true); |
| delegate_->OnStreamReady(used_ssl_config, job->proxy_info(), |
| std::move(stream)); |
| } |
| |
| void HttpStreamFactory::JobController::OnBidirectionalStreamImplReady( |
| Job* job, |
| const SSLConfig& used_ssl_config, |
| const ProxyInfo& used_proxy_info) { |
| DCHECK(job); |
| |
| if (IsJobOrphaned(job)) { |
| // We have bound a job to the associated HttpStreamRequest, |job| has been |
| // orphaned. |
| OnOrphanedJobComplete(job); |
| return; |
| } |
| |
| MarkRequestComplete(job); |
| |
| if (!request_) |
| return; |
| std::unique_ptr<BidirectionalStreamImpl> stream = |
| job->ReleaseBidirectionalStream(); |
| DCHECK(stream); |
| DCHECK(!is_websocket_); |
| DCHECK_EQ(HttpStreamRequest::BIDIRECTIONAL_STREAM, request_->stream_type()); |
| |
| OnJobSucceeded(job); |
| DCHECK(request_->completed()); |
| delegate_->OnBidirectionalStreamImplReady(used_ssl_config, used_proxy_info, |
| std::move(stream)); |
| } |
| |
| void HttpStreamFactory::JobController::OnWebSocketHandshakeStreamReady( |
| Job* job, |
| const SSLConfig& used_ssl_config, |
| const ProxyInfo& used_proxy_info, |
| std::unique_ptr<WebSocketHandshakeStreamBase> stream) { |
| DCHECK(job); |
| MarkRequestComplete(job); |
| |
| if (!request_) |
| return; |
| DCHECK(is_websocket_); |
| DCHECK_EQ(HttpStreamRequest::HTTP_STREAM, request_->stream_type()); |
| DCHECK(stream); |
| |
| OnJobSucceeded(job); |
| DCHECK(request_->completed()); |
| delegate_->OnWebSocketHandshakeStreamReady(used_ssl_config, used_proxy_info, |
| std::move(stream)); |
| } |
| |
| void HttpStreamFactory::JobController::OnStreamFailed( |
| Job* job, |
| int status, |
| const SSLConfig& used_ssl_config) { |
| DCHECK_NE(OK, status); |
| if (job->job_type() == MAIN) { |
| DCHECK_EQ(main_job_.get(), job); |
| main_job_net_error_ = status; |
| } else if (job->job_type() == ALTERNATIVE) { |
| DCHECK_EQ(alternative_job_.get(), job); |
| DCHECK_NE(kProtoUnknown, alternative_service_info_.protocol()); |
| alternative_job_net_error_ = status; |
| } else { |
| DCHECK_EQ(job->job_type(), DNS_ALPN_H3); |
| DCHECK_EQ(dns_alpn_h3_job_.get(), job); |
| dns_alpn_h3_job_net_error_ = status; |
| } |
| |
| MaybeResumeMainJob(job, base::TimeDelta()); |
| |
| if (IsJobOrphaned(job)) { |
| // We have bound a job to the associated HttpStreamRequest, |job| has been |
| // orphaned. |
| OnOrphanedJobComplete(job); |
| return; |
| } |
| |
| if (!request_) |
| return; |
| DCHECK_NE(OK, status); |
| DCHECK(job); |
| |
| if (!bound_job_) { |
| if (GetJobCount() >= 2) { |
| // Hey, we've got other jobs! Maybe one of them will succeed, let's just |
| // ignore this failure. |
| if (job->job_type() == MAIN) { |
| DCHECK_EQ(main_job_.get(), job); |
| main_job_.reset(); |
| } else if (job->job_type() == ALTERNATIVE) { |
| DCHECK_EQ(alternative_job_.get(), job); |
| alternative_job_.reset(); |
| } else { |
| DCHECK_EQ(job->job_type(), DNS_ALPN_H3); |
| DCHECK_EQ(dns_alpn_h3_job_.get(), job); |
| dns_alpn_h3_job_.reset(); |
| } |
| return; |
| } else { |
| BindJob(job); |
| } |
| } |
| |
| status = ReconsiderProxyAfterError(job, status); |
| if (next_state_ == STATE_RESOLVE_PROXY_COMPLETE) { |
| if (status == ERR_IO_PENDING) |
| return; |
| DCHECK_EQ(OK, status); |
| RunLoop(status); |
| return; |
| } |
| |
| HistogramProxyUsed(job->proxy_info(), /*success=*/false); |
| delegate_->OnStreamFailed(status, *job->net_error_details(), used_ssl_config, |
| job->proxy_info(), job->resolve_error_info()); |
| } |
| |
| void HttpStreamFactory::JobController::OnFailedOnDefaultNetwork(Job* job) { |
| if (job->job_type() == ALTERNATIVE) { |
| DCHECK_EQ(alternative_job_.get(), job); |
| alternative_job_failed_on_default_network_ = true; |
| } else { |
| DCHECK_EQ(job->job_type(), DNS_ALPN_H3); |
| DCHECK_EQ(dns_alpn_h3_job_.get(), job); |
| dns_alpn_h3_job_failed_on_default_network_ = true; |
| } |
| } |
| |
| void HttpStreamFactory::JobController::OnCertificateError( |
| Job* job, |
| int status, |
| const SSLConfig& used_ssl_config, |
| const SSLInfo& ssl_info) { |
| MaybeResumeMainJob(job, base::TimeDelta()); |
| |
| if (IsJobOrphaned(job)) { |
| // We have bound a job to the associated HttpStreamRequest, |job| has been |
| // orphaned. |
| OnOrphanedJobComplete(job); |
| return; |
| } |
| |
| if (!request_) |
| return; |
| DCHECK_NE(OK, status); |
| if (!bound_job_) |
| BindJob(job); |
| |
| delegate_->OnCertificateError(status, used_ssl_config, ssl_info); |
| } |
| |
| void HttpStreamFactory::JobController::OnNeedsClientAuth( |
| Job* job, |
| const SSLConfig& used_ssl_config, |
| SSLCertRequestInfo* cert_info) { |
| MaybeResumeMainJob(job, base::TimeDelta()); |
| |
| if (IsJobOrphaned(job)) { |
| // We have bound a job to the associated HttpStreamRequest, |job| has been |
| // orphaned. |
| OnOrphanedJobComplete(job); |
| return; |
| } |
| if (!request_) |
| return; |
| if (!bound_job_) |
| BindJob(job); |
| |
| delegate_->OnNeedsClientAuth(used_ssl_config, cert_info); |
| } |
| |
| void HttpStreamFactory::JobController::OnNeedsProxyAuth( |
| Job* job, |
| const HttpResponseInfo& proxy_response, |
| const SSLConfig& used_ssl_config, |
| const ProxyInfo& used_proxy_info, |
| HttpAuthController* auth_controller) { |
| MaybeResumeMainJob(job, base::TimeDelta()); |
| |
| if (IsJobOrphaned(job)) { |
| // We have bound a job to the associated HttpStreamRequest, |job| has been |
| // orphaned. |
| OnOrphanedJobComplete(job); |
| return; |
| } |
| |
| if (!request_) |
| return; |
| if (!bound_job_) |
| BindJob(job); |
| delegate_->OnNeedsProxyAuth(proxy_response, used_ssl_config, used_proxy_info, |
| auth_controller); |
| } |
| |
| void HttpStreamFactory::JobController::OnPreconnectsComplete(Job* job, |
| int result) { |
| // Preconnects only run as `main_job_`, never `alternative_job_` or |
| // `dns_alpn_h3_job_`. |
| DCHECK_EQ(main_job_.get(), job); |
| |
| // If the job failed because there were no matching HTTPS records in DNS, run |
| // the backup job. A TCP-based protocol may work instead. |
| if (result == ERR_DNS_NO_MATCHING_SUPPORTED_ALPN && preconnect_backup_job_) { |
| DCHECK_EQ(job->job_type(), PRECONNECT_DNS_ALPN_H3); |
| main_job_ = std::move(preconnect_backup_job_); |
| main_job_->Preconnect(num_streams_); |
| return; |
| } |
| |
| main_job_.reset(); |
| preconnect_backup_job_.reset(); |
| ResetErrorStatusForJobs(); |
| factory_->OnPreconnectsCompleteInternal(); |
| MaybeNotifyFactoryOfCompletion(); |
| } |
| |
| void HttpStreamFactory::JobController::OnOrphanedJobComplete(const Job* job) { |
| if (job->job_type() == MAIN) { |
| DCHECK_EQ(main_job_.get(), job); |
| main_job_.reset(); |
| } else if (job->job_type() == ALTERNATIVE) { |
| DCHECK_EQ(alternative_job_.get(), job); |
| alternative_job_.reset(); |
| } else { |
| DCHECK_EQ(job->job_type(), DNS_ALPN_H3); |
| DCHECK_EQ(dns_alpn_h3_job_.get(), job); |
| dns_alpn_h3_job_.reset(); |
| } |
| |
| MaybeNotifyFactoryOfCompletion(); |
| } |
| |
| void HttpStreamFactory::JobController::AddConnectionAttemptsToRequest( |
| Job* job, |
| const ConnectionAttempts& attempts) { |
| if (is_preconnect_ || IsJobOrphaned(job)) |
| return; |
| |
| request_->AddConnectionAttempts(attempts); |
| } |
| |
| void HttpStreamFactory::JobController::ResumeMainJobLater( |
| const base::TimeDelta& delay) { |
| net_log_.AddEventWithInt64Params(NetLogEventType::HTTP_STREAM_JOB_DELAYED, |
| "delay", delay.InMilliseconds()); |
| resume_main_job_callback_.Reset( |
| base::BindOnce(&HttpStreamFactory::JobController::ResumeMainJob, |
| ptr_factory_.GetWeakPtr())); |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, resume_main_job_callback_.callback(), delay); |
| } |
| |
| void HttpStreamFactory::JobController::ResumeMainJob() { |
| DCHECK(main_job_); |
| |
| if (main_job_is_resumed_) { |
| return; |
| } |
| main_job_is_resumed_ = true; |
| main_job_->net_log().AddEventWithInt64Params( |
| NetLogEventType::HTTP_STREAM_JOB_RESUMED, "delay", |
| main_job_wait_time_.InMilliseconds()); |
| |
| main_job_->Resume(); |
| main_job_wait_time_ = base::TimeDelta(); |
| } |
| |
| void HttpStreamFactory::JobController::ResetErrorStatusForJobs() { |
| main_job_net_error_ = OK; |
| alternative_job_net_error_ = OK; |
| alternative_job_failed_on_default_network_ = false; |
| dns_alpn_h3_job_net_error_ = OK; |
| dns_alpn_h3_job_failed_on_default_network_ = false; |
| } |
| |
| void HttpStreamFactory::JobController::MaybeResumeMainJob( |
| Job* job, |
| const base::TimeDelta& delay) { |
| DCHECK(delay == base::TimeDelta() || delay == main_job_wait_time_); |
| DCHECK(job == main_job_.get() || job == alternative_job_.get() || |
| job == dns_alpn_h3_job_.get()); |
| |
| if (job == main_job_.get()) |
| return; |
| if (job == dns_alpn_h3_job_.get() && alternative_job_) { |
| return; |
| } |
| if (!main_job_) |
| return; |
| |
| main_job_is_blocked_ = false; |
| |
| if (!main_job_->is_waiting()) { |
| // There are two cases where the main job is not in WAIT state: |
| // 1) The main job hasn't got to waiting state, do not yet post a task to |
| // resume since that will happen in ShouldWait(). |
| // 2) The main job has passed waiting state, so the main job does not need |
| // to be resumed. |
| return; |
| } |
| |
| main_job_wait_time_ = delay; |
| |
| ResumeMainJobLater(main_job_wait_time_); |
| } |
| |
| void HttpStreamFactory::JobController::OnConnectionInitialized(Job* job, |
| int rv) { |
| if (rv != OK) { |
| // Resume the main job as there's an error raised in connection |
| // initiation. |
| return MaybeResumeMainJob(job, main_job_wait_time_); |
| } |
| } |
| |
| bool HttpStreamFactory::JobController::ShouldWait(Job* job) { |
| // The alternative job never waits. |
| if (job == alternative_job_.get() || job == dns_alpn_h3_job_.get()) |
| return false; |
| DCHECK_EQ(main_job_.get(), job); |
| if (main_job_is_blocked_) |
| return true; |
| |
| if (main_job_wait_time_.is_zero()) |
| return false; |
| |
| ResumeMainJobLater(main_job_wait_time_); |
| return true; |
| } |
| |
| const NetLogWithSource* HttpStreamFactory::JobController::GetNetLog() const { |
| return &net_log_; |
| } |
| |
| void HttpStreamFactory::JobController::MaybeSetWaitTimeForMainJob( |
| const base::TimeDelta& delay) { |
| if (main_job_is_blocked_) { |
| const bool has_available_spdy_session = |
| main_job_->HasAvailableSpdySession(); |
| if (!delay_main_job_with_available_spdy_session_ && |
| has_available_spdy_session) { |
| main_job_wait_time_ = base::TimeDelta(); |
| } else { |
| main_job_wait_time_ = |
| std::min(delay, base::Seconds(kMaxDelayTimeForMainJobSecs)); |
| } |
| if (has_available_spdy_session) { |
| UMA_HISTOGRAM_TIMES("Net.HttpJob.MainJobWaitTimeWithAvailableSpdySession", |
| main_job_wait_time_); |
| } else { |
| UMA_HISTOGRAM_TIMES( |
| "Net.HttpJob.MainJobWaitTimeWithoutAvailableSpdySession", |
| main_job_wait_time_); |
| } |
| } |
| } |
| |
| bool HttpStreamFactory::JobController::HasPendingMainJob() const { |
| return main_job_.get() != nullptr; |
| } |
| |
| bool HttpStreamFactory::JobController::HasPendingAltJob() const { |
| return alternative_job_.get() != nullptr; |
| } |
| |
| WebSocketHandshakeStreamBase::CreateHelper* |
| HttpStreamFactory::JobController::websocket_handshake_stream_create_helper() { |
| DCHECK(request_); |
| return request_->websocket_handshake_stream_create_helper(); |
| } |
| |
| void HttpStreamFactory::JobController::OnIOComplete(int result) { |
| RunLoop(result); |
| } |
| |
| void HttpStreamFactory::JobController::RunLoop(int result) { |
| int rv = DoLoop(result); |
| if (rv == ERR_IO_PENDING) |
| return; |
| if (rv != OK) { |
| // DoLoop can only fail during proxy resolution step which happens before |
| // any jobs are created. Notify |request_| of the failure one message loop |
| // iteration later to avoid re-entrancy. |
| DCHECK(!main_job_); |
| DCHECK(!alternative_job_); |
| DCHECK(!dns_alpn_h3_job_); |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&HttpStreamFactory::JobController::NotifyRequestFailed, |
| ptr_factory_.GetWeakPtr(), rv)); |
| } |
| } |
| |
| int HttpStreamFactory::JobController::DoLoop(int rv) { |
| DCHECK_NE(next_state_, STATE_NONE); |
| do { |
| State state = next_state_; |
| next_state_ = STATE_NONE; |
| switch (state) { |
| case STATE_RESOLVE_PROXY: |
| DCHECK_EQ(OK, rv); |
| rv = DoResolveProxy(); |
| break; |
| case STATE_RESOLVE_PROXY_COMPLETE: |
| rv = DoResolveProxyComplete(rv); |
| break; |
| case STATE_CREATE_JOBS: |
| DCHECK_EQ(OK, rv); |
| rv = DoCreateJobs(); |
| break; |
| default: |
| NOTREACHED() << "bad state"; |
| break; |
| } |
| } while (next_state_ != STATE_NONE && rv != ERR_IO_PENDING); |
| return rv; |
| } |
| |
| int HttpStreamFactory::JobController::DoResolveProxy() { |
| DCHECK(!proxy_resolve_request_); |
| DCHECK(session_); |
| |
| next_state_ = STATE_RESOLVE_PROXY_COMPLETE; |
| |
| if (request_info_.load_flags & LOAD_BYPASS_PROXY) { |
| proxy_info_.UseDirect(); |
| return OK; |
| } |
| |
| GURL origin_url = request_info_.url; |
| RewriteUrlWithHostMappingRules(origin_url); |
| |
| CompletionOnceCallback io_callback = |
| base::BindOnce(&JobController::OnIOComplete, base::Unretained(this)); |
| return session_->proxy_resolution_service()->ResolveProxy( |
| origin_url, request_info_.method, request_info_.network_anonymization_key, |
| &proxy_info_, std::move(io_callback), &proxy_resolve_request_, net_log_); |
| } |
| |
| int HttpStreamFactory::JobController::DoResolveProxyComplete(int rv) { |
| DCHECK_NE(ERR_IO_PENDING, rv); |
| |
| proxy_resolve_request_ = nullptr; |
| net_log_.AddEvent( |
| NetLogEventType::HTTP_STREAM_JOB_CONTROLLER_PROXY_SERVER_RESOLVED, [&] { |
| return NetLogHttpStreamJobProxyServerResolved( |
| proxy_info_.is_empty() ? ProxyServer() |
| : proxy_info_.proxy_server()); |
| }); |
| |
| if (rv != OK) |
| return rv; |
| // Remove unsupported proxies from the list. |
| int supported_proxies = ProxyServer::SCHEME_DIRECT | |
| ProxyServer::SCHEME_HTTP | ProxyServer::SCHEME_HTTPS | |
| ProxyServer::SCHEME_SOCKS4 | |
| ProxyServer::SCHEME_SOCKS5; |
| // WebSockets is not supported over QUIC. |
| if (session_->IsQuicEnabled() && !is_websocket_) |
| supported_proxies |= ProxyServer::SCHEME_QUIC; |
| proxy_info_.RemoveProxiesWithoutScheme(supported_proxies); |
| |
| if (proxy_info_.is_empty()) { |
| // No proxies/direct to choose from. |
| return ERR_NO_SUPPORTED_PROXIES; |
| } |
| |
| next_state_ = STATE_CREATE_JOBS; |
| return rv; |
| } |
| |
| int HttpStreamFactory::JobController::DoCreateJobs() { |
| DCHECK(!main_job_); |
| DCHECK(!alternative_job_); |
| DCHECK(request_info_.url.is_valid()); |
| DCHECK(request_info_.url.IsStandard()); |
| |
| GURL origin_url = request_info_.url; |
| RewriteUrlWithHostMappingRules(origin_url); |
| |
| url::SchemeHostPort destination(origin_url); |
| DCHECK(destination.IsValid()); |
| ConvertWsToHttp(destination); |
| |
| // Create an alternative job if alternative service is set up for this domain, |
| // but only if we'll be speaking directly to the server, since QUIC through |
| // proxies is not supported. |
| if (proxy_info_.is_direct()) { |
| alternative_service_info_ = |
| GetAlternativeServiceInfoFor(request_info_, delegate_, stream_type_); |
| } |
| quic::ParsedQuicVersion quic_version = quic::ParsedQuicVersion::Unsupported(); |
| if (alternative_service_info_.protocol() == kProtoQUIC) { |
| quic_version = |
| SelectQuicVersion(alternative_service_info_.advertised_versions()); |
| DCHECK_NE(quic_version, quic::ParsedQuicVersion::Unsupported()); |
| } |
| const bool dns_alpn_h3_job_enabled = |
| enable_alternative_services_ && |
| session_->params().use_dns_https_svcb_alpn && |
| base::EqualsCaseInsensitiveASCII(origin_url.scheme(), |
| url::kHttpsScheme) && |
| session_->IsQuicEnabled() && proxy_info_.is_direct() && |
| !session_->http_server_properties()->IsAlternativeServiceBroken( |
| GetAlternativeServiceForDnsJob(origin_url), |
| request_info_.network_anonymization_key); |
| |
| if (is_preconnect_) { |
| // Due to how the socket pools handle priorities and idle sockets, only IDLE |
| // priority currently makes sense for preconnects. The priority for |
| // preconnects is currently ignored (see RequestSocketsForPool()), but could |
| // be used at some point for proxy resolution or something. |
| // Note: When `dns_alpn_h3_job_enabled` is true, we create a |
| // PRECONNECT_DNS_ALPN_H3 job. If no matching HTTPS DNS ALPN records are |
| // received, the PRECONNECT_DNS_ALPN_H3 job will fail with |
| // ERR_DNS_NO_MATCHING_SUPPORTED_ALPN, and `preconnect_backup_job_` will |
| // be started in OnPreconnectsComplete(). |
| std::unique_ptr<Job> preconnect_job = job_factory_->CreateJob( |
| this, dns_alpn_h3_job_enabled ? PRECONNECT_DNS_ALPN_H3 : PRECONNECT, |
| session_, request_info_, IDLE, proxy_info_, server_ssl_config_, |
| proxy_ssl_config_, destination, origin_url, is_websocket_, |
| enable_ip_based_pooling_, net_log_.net_log()); |
| // When there is an valid alternative service info, and `preconnect_job` |
| // has no existing QUIC session, create a job for the alternative service. |
| if (alternative_service_info_.protocol() != kProtoUnknown && |
| !preconnect_job->HasAvailableQuicSession()) { |
| GURL alternative_url = CreateAltSvcUrl( |
| origin_url, alternative_service_info_.host_port_pair()); |
| RewriteUrlWithHostMappingRules(alternative_url); |
| |
| url::SchemeHostPort alternative_destination = |
| url::SchemeHostPort(alternative_url); |
| ConvertWsToHttp(alternative_destination); |
| |
| main_job_ = job_factory_->CreateJob( |
| this, PRECONNECT, session_, request_info_, IDLE, proxy_info_, |
| server_ssl_config_, proxy_ssl_config_, |
| std::move(alternative_destination), origin_url, is_websocket_, |
| enable_ip_based_pooling_, session_->net_log(), |
| alternative_service_info_.protocol(), quic_version); |
| } else { |
| main_job_ = std::move(preconnect_job); |
| |
| if (dns_alpn_h3_job_enabled) { |
| preconnect_backup_job_ = job_factory_->CreateJob( |
| this, PRECONNECT, session_, request_info_, IDLE, proxy_info_, |
| server_ssl_config_, proxy_ssl_config_, std::move(destination), |
| origin_url, is_websocket_, enable_ip_based_pooling_, |
| net_log_.net_log()); |
| } |
| } |
| main_job_->Preconnect(num_streams_); |
| return OK; |
| } |
| main_job_ = job_factory_->CreateJob( |
| this, MAIN, session_, request_info_, priority_, proxy_info_, |
| server_ssl_config_, proxy_ssl_config_, std::move(destination), origin_url, |
| is_websocket_, enable_ip_based_pooling_, net_log_.net_log()); |
| |
| // Alternative Service can only be set for HTTPS requests while Alternative |
| // Proxy is set for HTTP requests. |
| // The main job may use HTTP/3 if the origin is specified in |
| // `--origin-to-force-quic-on` switch. In that case, do not create |
| // `alternative_job_` and `dns_alpn_h3_job_`. |
| if ((alternative_service_info_.protocol() != kProtoUnknown) && |
| !main_job_->using_quic()) { |
| DCHECK(request_info_.url.SchemeIs(url::kHttpsScheme)); |
| DCHECK(!is_websocket_); |
| DVLOG(1) << "Selected alternative service (host: " |
| << alternative_service_info_.host_port_pair().host() |
| << " port: " << alternative_service_info_.host_port_pair().port() |
| << " version: " << quic_version << ")"; |
| |
| GURL alternative_url = |
| CreateAltSvcUrl(origin_url, alternative_service_info_.host_port_pair()); |
| RewriteUrlWithHostMappingRules(alternative_url); |
| |
| url::SchemeHostPort alternative_destination = |
| url::SchemeHostPort(alternative_url); |
| ConvertWsToHttp(alternative_destination); |
| |
| alternative_job_ = job_factory_->CreateJob( |
| this, ALTERNATIVE, session_, request_info_, priority_, proxy_info_, |
| server_ssl_config_, proxy_ssl_config_, |
| std::move(alternative_destination), origin_url, is_websocket_, |
| enable_ip_based_pooling_, net_log_.net_log(), |
| alternative_service_info_.protocol(), quic_version); |
| } |
| |
| if (dns_alpn_h3_job_enabled && !main_job_->using_quic()) { |
| DCHECK(!is_websocket_); |
| url::SchemeHostPort dns_alpn_h3_destination = |
| url::SchemeHostPort(origin_url); |
| dns_alpn_h3_job_ = job_factory_->CreateJob( |
| this, DNS_ALPN_H3, session_, request_info_, priority_, proxy_info_, |
| server_ssl_config_, proxy_ssl_config_, |
| std::move(dns_alpn_h3_destination), origin_url, is_websocket_, |
| enable_ip_based_pooling_, net_log_.net_log()); |
| } |
| |
| ClearInappropriateJobs(); |
| |
| if (main_job_ && (alternative_job_ || |
| (dns_alpn_h3_job_ && |
| (!main_job_->TargettedSocketGroupHasActiveSocket() && |
| !main_job_->HasAvailableSpdySession())))) { |
| // We don't block |main_job_| when |alternative_job_| doesn't exists and |
| // |dns_alpn_h3_job_| exists and an active socket is available for |
| // |main_job_|. This is intended to make the fallback logic faster. |
| main_job_is_blocked_ = true; |
| } |
| |
| if (alternative_job_) { |
| alternative_job_->Start(request_->stream_type()); |
| } |
| |
| if (dns_alpn_h3_job_) { |
| dns_alpn_h3_job_->Start(request_->stream_type()); |
| } |
| |
| if (main_job_) { |
| main_job_->Start(request_->stream_type()); |
| } |
| return OK; |
| } |
| |
| void HttpStreamFactory::JobController::ClearInappropriateJobs() { |
| if (dns_alpn_h3_job_ && dns_alpn_h3_job_->HasAvailableQuicSession()) { |
| // Clear |main_job_| and |alternative_job_| here not to start them when |
| // there is an active session available for |dns_alpn_h3_job_|. |
| main_job_.reset(); |
| alternative_job_.reset(); |
| } |
| |
| if (alternative_job_ && dns_alpn_h3_job_ && |
| (alternative_job_->HasAvailableQuicSession() || |
| (alternative_service_info_.alternative_service() == |
| GetAlternativeServiceForDnsJob(request_info_.url)))) { |
| // Clear |dns_alpn_h3_job_|, when there is an active session available for |
| // |alternative_job_| or |alternative_job_| was created for the same |
| // destination. |
| dns_alpn_h3_job_.reset(); |
| } |
| } |
| |
| void HttpStreamFactory::JobController::BindJob(Job* job) { |
| DCHECK(request_); |
| DCHECK(job); |
| DCHECK(job == alternative_job_.get() || job == main_job_.get() || |
| job == dns_alpn_h3_job_.get()); |
| DCHECK(!job_bound_); |
| DCHECK(!bound_job_); |
| |
| job_bound_ = true; |
| bound_job_ = job; |
| |
| request_->net_log().AddEventReferencingSource( |
| NetLogEventType::HTTP_STREAM_REQUEST_BOUND_TO_JOB, |
| job->net_log().source()); |
| job->net_log().AddEventReferencingSource( |
| NetLogEventType::HTTP_STREAM_JOB_BOUND_TO_REQUEST, |
| request_->net_log().source()); |
| |
| OrphanUnboundJob(); |
| } |
| |
| void HttpStreamFactory::JobController::OrphanUnboundJob() { |
| DCHECK(request_); |
| DCHECK(bound_job_); |
| |
| if (bound_job_->job_type() == MAIN) { |
| // Allow |alternative_job_| and |dns_alpn_h3_job_| to run to completion, |
| // rather than resetting them to check if there is any broken alternative |
| // service to report. OnOrphanedJobComplete() will clean up |this| when the |
| // jobs complete. |
| if (alternative_job_) { |
| DCHECK(!is_websocket_); |
| alternative_job_->Orphan(); |
| } |
| if (dns_alpn_h3_job_) { |
| DCHECK(!is_websocket_); |
| dns_alpn_h3_job_->Orphan(); |
| } |
| return; |
| } |
| |
| if (bound_job_->job_type() == ALTERNATIVE) { |
| if (!alternative_job_failed_on_default_network_ && !dns_alpn_h3_job_) { |
| // |request_| is bound to the alternative job and the alternative job |
| // succeeds on the default network, and there is no DNS alt job. This |
| // means that the main job is no longer needed, so cancel it now. Pending |
| // ConnectJobs will return established sockets to socket pools if |
| // applicable. |
| // https://crbug.com/757548. |
| // The main job still needs to run if the alternative job succeeds on the |
| // alternate network in order to figure out whether QUIC should be marked |
| // as broken until the default network changes. And also the main job |
| // still needs to run if the DNS alt job exists to figure out whether |
| // the DNS alpn service is broken. |
| DCHECK(!main_job_ || (alternative_job_net_error_ == OK)); |
| main_job_.reset(); |
| } |
| // Allow |dns_alpn_h3_job_| to run to completion, rather than resetting |
| // it to check if there is any broken alternative service to report. |
| // OnOrphanedJobComplete() will clean up |this| when the job completes. |
| if (dns_alpn_h3_job_) { |
| DCHECK(!is_websocket_); |
| dns_alpn_h3_job_->Orphan(); |
| } |
| } |
| if (bound_job_->job_type() == DNS_ALPN_H3) { |
| if (!dns_alpn_h3_job_failed_on_default_network_ && !alternative_job_) { |
| DCHECK(!main_job_ || (dns_alpn_h3_job_net_error_ == OK)); |
| main_job_.reset(); |
| } |
| // Allow |alternative_job_| to run to completion, rather than resetting |
| // it to check if there is any broken alternative service to report. |
| // OnOrphanedJobComplete() will clean up |this| when the job completes. |
| if (alternative_job_) { |
| DCHECK(!is_websocket_); |
| alternative_job_->Orphan(); |
| } |
| } |
| } |
| |
| void HttpStreamFactory::JobController::OnJobSucceeded(Job* job) { |
| DCHECK(job); |
| if (!bound_job_) { |
| BindJob(job); |
| return; |
| } |
| } |
| |
| void HttpStreamFactory::JobController::MarkRequestComplete(Job* job) { |
| if (request_) { |
| AlternateProtocolUsage alternate_protocol_usage = |
| CalculateAlternateProtocolUsage(job); |
| request_->Complete(job->was_alpn_negotiated(), job->negotiated_protocol(), |
| alternate_protocol_usage, job->using_spdy()); |
| ReportAlternateProtocolUsage(alternate_protocol_usage, |
| HasGoogleHost(job->origin_url())); |
| } |
| } |
| |
| void HttpStreamFactory::JobController::MaybeReportBrokenAlternativeService( |
| const AlternativeService& alt_service, |
| int alt_job_net_error, |
| bool alt_job_failed_on_default_network, |
| const std::string& histogram_name_for_failure) { |
| // If alternative job succeeds on the default network, no brokenness to |
| // report. |
| if (alt_job_net_error == OK && !alt_job_failed_on_default_network) |
| return; |
| |
| // No brokenness to report if the main job fails. |
| if (main_job_net_error_ != OK) |
| return; |
| |
| // No need to record DNS_NO_MATCHING_SUPPORTED_ALPN error. |
| if (alt_job_net_error == ERR_DNS_NO_MATCHING_SUPPORTED_ALPN) { |
| return; |
| } |
| |
| if (alt_job_failed_on_default_network && alt_job_net_error == OK) { |
| // Alternative job failed on the default network but succeeds on the |
| // non-default network, mark alternative service broken until the default |
| // network changes. |
| session_->http_server_properties() |
| ->MarkAlternativeServiceBrokenUntilDefaultNetworkChanges( |
| alt_service, request_info_.network_anonymization_key); |
| return; |
| } |
| |
| if (alt_job_net_error == ERR_NETWORK_CHANGED || |
| alt_job_net_error == ERR_INTERNET_DISCONNECTED || |
| (alt_job_net_error == ERR_NAME_NOT_RESOLVED && |
| request_info_.url.host() == alt_service.host)) { |
| // No need to mark alternative service as broken. |
| return; |
| } |
| |
| // Report brokenness if alternative job failed. |
| base::UmaHistogramSparse(histogram_name_for_failure, -alt_job_net_error); |
| |
| HistogramBrokenAlternateProtocolLocation( |
| BROKEN_ALTERNATE_PROTOCOL_LOCATION_HTTP_STREAM_FACTORY_JOB_ALT); |
| session_->http_server_properties()->MarkAlternativeServiceBroken( |
| alt_service, request_info_.network_anonymization_key); |
| } |
| |
| void HttpStreamFactory::JobController::MaybeNotifyFactoryOfCompletion() { |
| if (main_job_ || alternative_job_ || dns_alpn_h3_job_) |
| return; |
| |
| // All jobs are gone. |
| // Report brokenness for the alternate jobs if apply. |
| MaybeReportBrokenAlternativeService( |
| alternative_service_info_.alternative_service(), |
| alternative_job_net_error_, alternative_job_failed_on_default_network_, |
| "Net.AlternateServiceFailed"); |
| // Report for the DNS alt job if apply. |
| MaybeReportBrokenAlternativeService( |
| GetAlternativeServiceForDnsJob(request_info_.url), |
| dns_alpn_h3_job_net_error_, dns_alpn_h3_job_failed_on_default_network_, |
| "Net.AlternateServiceForDnsAlpnH3Failed"); |
| |
| // Reset error status for Jobs after reporting brokenness to avoid redundant |
| // reporting. |
| ResetErrorStatusForJobs(); |
| |
| if (request_) |
| return; |
| DCHECK(!bound_job_); |
| factory_->OnJobControllerComplete(this); |
| } |
| |
| void HttpStreamFactory::JobController::NotifyRequestFailed(int rv) { |
| if (!request_) |
| return; |
| delegate_->OnStreamFailed(rv, NetErrorDetails(), server_ssl_config_, |
| ProxyInfo(), ResolveErrorInfo()); |
| } |
| |
| void HttpStreamFactory::JobController::RewriteUrlWithHostMappingRules( |
| GURL& url) { |
| session_->params().host_mapping_rules.RewriteUrl(url); |
| } |
| |
| AlternativeServiceInfo |
| HttpStreamFactory::JobController::GetAlternativeServiceInfoFor( |
| const HttpRequestInfo& request_info, |
| HttpStreamRequest::Delegate* delegate, |
| HttpStreamRequest::StreamType stream_type) { |
| if (!enable_alternative_services_) |
| return AlternativeServiceInfo(); |
| |
| AlternativeServiceInfo alternative_service_info = |
| GetAlternativeServiceInfoInternal(request_info, delegate, stream_type); |
| AlternativeServiceType type; |
| if (alternative_service_info.protocol() == kProtoUnknown) { |
| type = NO_ALTERNATIVE_SERVICE; |
| } else if (alternative_service_info.protocol() == kProtoQUIC) { |
| if (request_info.url.host_piece() == |
| alternative_service_info.alternative_service().host) { |
| type = QUIC_SAME_DESTINATION; |
| } else { |
| type = QUIC_DIFFERENT_DESTINATION; |
| } |
| } else { |
| if (request_info.url.host_piece() == |
| alternative_service_info.alternative_service().host) { |
| type = NOT_QUIC_SAME_DESTINATION; |
| } else { |
| type = NOT_QUIC_DIFFERENT_DESTINATION; |
| } |
| } |
| UMA_HISTOGRAM_ENUMERATION("Net.AlternativeServiceTypeForRequest", type, |
| MAX_ALTERNATIVE_SERVICE_TYPE); |
| return alternative_service_info; |
| } |
| |
| AlternativeServiceInfo |
| HttpStreamFactory::JobController::GetAlternativeServiceInfoInternal( |
| const HttpRequestInfo& request_info, |
| HttpStreamRequest::Delegate* delegate, |
| HttpStreamRequest::StreamType stream_type) { |
| GURL original_url = request_info.url; |
| |
| if (!original_url.SchemeIs(url::kHttpsScheme)) |
| return AlternativeServiceInfo(); |
| |
| HttpServerProperties& http_server_properties = |
| *session_->http_server_properties(); |
| const AlternativeServiceInfoVector alternative_service_info_vector = |
| http_server_properties.GetAlternativeServiceInfos( |
| url::SchemeHostPort(original_url), |
| request_info.network_anonymization_key); |
| #if defined(STARBOARD) |
| // This block of code suggests QUIC connection for initial requests to a |
| // new host. This method is proven to provide performance benefit while still |
| // enabling Cobalt network module to fall back on TCP connection when QUIC |
| // fails or is too slow. |
| if (alternative_service_info_vector.empty() && session_->IsQuicEnabled() && |
| session_->UseQuicForUnknownOrigin()) { |
| url::SchemeHostPort origin(original_url); |
| // Leave the port restriction only in production builds to simplify testing |
| #if defined(COBALT_BUILD_TYPE_GOLD) |
| if (origin.port() == kDefaultQUICServerPort) { |
| #endif |
| quic::ParsedQuicVersionVector versions = quic::AllSupportedVersions(); |
| return AlternativeServiceInfo::CreateQuicAlternativeServiceInfo( |
| AlternativeService(net::kProtoQUIC, origin.host(), |
| origin.port()), |
| base::Time::Max(), versions); |
| #if defined(COBALT_BUILD_TYPE_GOLD) |
| } |
| #endif |
| return AlternativeServiceInfo(); |
| } |
| #else |
| if (alternative_service_info_vector.empty()) |
| return AlternativeServiceInfo(); |
| #endif |
| |
| bool quic_advertised = false; |
| bool quic_all_broken = true; |
| |
| // First alternative service that is not marked as broken. |
| AlternativeServiceInfo first_alternative_service_info; |
| |
| bool is_any_broken = false; |
| for (const AlternativeServiceInfo& alternative_service_info : |
| alternative_service_info_vector) { |
| DCHECK(IsAlternateProtocolValid(alternative_service_info.protocol())); |
| if (!quic_advertised && alternative_service_info.protocol() == kProtoQUIC) |
| quic_advertised = true; |
| const bool is_broken = http_server_properties.IsAlternativeServiceBroken( |
| alternative_service_info.alternative_service(), |
| request_info.network_anonymization_key); |
| net_log_.AddEvent( |
| NetLogEventType::HTTP_STREAM_JOB_CONTROLLER_ALT_SVC_FOUND, [&] { |
| return NetLogAltSvcParams(&alternative_service_info, is_broken); |
| }); |
| if (is_broken) { |
| if (!is_any_broken) { |
| // Only log the broken alternative service once per request. |
| is_any_broken = true; |
| HistogramAlternateProtocolUsage(ALTERNATE_PROTOCOL_USAGE_BROKEN, |
| HasGoogleHost(original_url)); |
| } |
| continue; |
| } |
| |
| // Some shared unix systems may have user home directories (like |
| // http://foo.com/~mike) which allow users to emit headers. This is a bad |
| // idea already, but with Alternate-Protocol, it provides the ability for a |
| // single user on a multi-user system to hijack the alternate protocol. |
| // These systems also enforce ports <1024 as restricted ports. So don't |
| // allow protocol upgrades to user-controllable ports. |
| const int kUnrestrictedPort = 1024; |
| if (!session_->params().enable_user_alternate_protocol_ports && |
| (alternative_service_info.alternative_service().port >= |
| kUnrestrictedPort && |
| original_url.EffectiveIntPort() < kUnrestrictedPort)) |
| continue; |
| |
| if (alternative_service_info.protocol() == kProtoHTTP2) { |
| if (!session_->params().enable_http2_alternative_service) |
| continue; |
| |
| // Cache this entry if we don't have a non-broken Alt-Svc yet. |
| if (first_alternative_service_info.protocol() == kProtoUnknown) |
| first_alternative_service_info = alternative_service_info; |
| continue; |
| } |
| |
| DCHECK_EQ(kProtoQUIC, alternative_service_info.protocol()); |
| quic_all_broken = false; |
| if (!session_->IsQuicEnabled()) |
| continue; |
| |
| if (!original_url.SchemeIs(url::kHttpsScheme)) |
| continue; |
| |
| // If there is no QUIC version in the advertised versions that is |
| // supported, ignore this entry. |
| if (SelectQuicVersion(alternative_service_info.advertised_versions()) == |
| quic::ParsedQuicVersion::Unsupported()) |
| continue; |
| |
| // Check whether there is an existing QUIC session to use for this origin. |
| GURL mapped_origin = original_url; |
| RewriteUrlWithHostMappingRules(mapped_origin); |
| QuicSessionKey session_key( |
| HostPortPair::FromURL(mapped_origin), request_info.privacy_mode, |
| request_info.socket_tag, request_info.network_anonymization_key, |
| request_info.secure_dns_policy, /*require_dns_https_alpn=*/false); |
| |
| GURL destination = CreateAltSvcUrl( |
| original_url, alternative_service_info.host_port_pair()); |
| if (session_key.host() != destination.host_piece() && |
| !session_->context().quic_context->params()->allow_remote_alt_svc) { |
| continue; |
| } |
| RewriteUrlWithHostMappingRules(destination); |
| |
| if (session_->quic_stream_factory()->CanUseExistingSession( |
| session_key, url::SchemeHostPort(destination))) |
| return alternative_service_info; |
| |
| if (!IsQuicAllowedForHost(destination.host())) |
| continue; |
| |
| // Cache this entry if we don't have a non-broken Alt-Svc yet. |
| if (first_alternative_service_info.protocol() == kProtoUnknown) |
| first_alternative_service_info = alternative_service_info; |
| } |
| |
| // Ask delegate to mark QUIC as broken for the origin. |
| if (quic_advertised && quic_all_broken && delegate != nullptr) |
| delegate->OnQuicBroken(); |
| |
| return first_alternative_service_info; |
| } |
| |
| quic::ParsedQuicVersion HttpStreamFactory::JobController::SelectQuicVersion( |
| const quic::ParsedQuicVersionVector& advertised_versions) { |
| const quic::ParsedQuicVersionVector& supported_versions = |
| session_->context().quic_context->params()->supported_versions; |
| if (advertised_versions.empty()) |
| return supported_versions[0]; |
| |
| for (const quic::ParsedQuicVersion& advertised : advertised_versions) { |
| for (const quic::ParsedQuicVersion& supported : supported_versions) { |
| if (supported == advertised) { |
| DCHECK_NE(quic::ParsedQuicVersion::Unsupported(), supported); |
| return supported; |
| } |
| } |
| } |
| |
| return quic::ParsedQuicVersion::Unsupported(); |
| } |
| |
| void HttpStreamFactory::JobController::ReportAlternateProtocolUsage( |
| AlternateProtocolUsage alternate_protocol_usage, |
| bool is_google_host) const { |
| DCHECK_LT(alternate_protocol_usage, ALTERNATE_PROTOCOL_USAGE_MAX); |
| HistogramAlternateProtocolUsage(alternate_protocol_usage, is_google_host); |
| } |
| |
| bool HttpStreamFactory::JobController::IsJobOrphaned(Job* job) const { |
| return !request_ || (job_bound_ && bound_job_ != job); |
| } |
| |
| AlternateProtocolUsage |
| HttpStreamFactory::JobController::CalculateAlternateProtocolUsage( |
| Job* job) const { |
| if ((main_job_ && alternative_job_) || dns_alpn_h3_job_) { |
| if (job == main_job_.get()) { |
| return ALTERNATE_PROTOCOL_USAGE_MAIN_JOB_WON_RACE; |
| } |
| if (job == alternative_job_.get()) { |
| if (job->using_existing_quic_session()) { |
| return ALTERNATE_PROTOCOL_USAGE_NO_RACE; |
| } |
| return ALTERNATE_PROTOCOL_USAGE_WON_RACE; |
| } |
| if (job == dns_alpn_h3_job_.get()) { |
| if (job->using_existing_quic_session()) { |
| return ALTERNATE_PROTOCOL_USAGE_DNS_ALPN_H3_JOB_WON_WITHOUT_RACE; |
| } |
| return ALTERNATE_PROTOCOL_USAGE_DNS_ALPN_H3_JOB_WON_RACE; |
| } |
| } |
| // TODO(crbug.com/1345536): Implement better logic to support uncovered cases. |
| return ALTERNATE_PROTOCOL_USAGE_UNSPECIFIED_REASON; |
| } |
| |
| int HttpStreamFactory::JobController::ReconsiderProxyAfterError(Job* job, |
| int error) { |
| // ReconsiderProxyAfterError() should only be called when the last job fails. |
| DCHECK_EQ(1, GetJobCount()); |
| DCHECK(!proxy_resolve_request_); |
| DCHECK(session_); |
| |
| if (!job->should_reconsider_proxy()) |
| return error; |
| |
| if (request_info_.load_flags & LOAD_BYPASS_PROXY) |
| return error; |
| |
| if (proxy_info_.is_secure_http_like()) { |
| session_->ssl_client_context()->ClearClientCertificate( |
| proxy_info_.proxy_server().host_port_pair()); |
| } |
| |
| if (!proxy_info_.Fallback(error, net_log_)) { |
| // If there is no more proxy to fallback to, fail the transaction |
| // with the last connection error we got. |
| return error; |
| } |
| |
| // Abandon all Jobs and start over. |
| job_bound_ = false; |
| bound_job_ = nullptr; |
| dns_alpn_h3_job_.reset(); |
| alternative_job_.reset(); |
| main_job_.reset(); |
| ResetErrorStatusForJobs(); |
| // Also resets states that related to the old main job. In particular, |
| // cancels |resume_main_job_callback_| so there won't be any delayed |
| // ResumeMainJob() left in the task queue. |
| resume_main_job_callback_.Cancel(); |
| main_job_is_resumed_ = false; |
| main_job_is_blocked_ = false; |
| |
| next_state_ = STATE_RESOLVE_PROXY_COMPLETE; |
| return OK; |
| } |
| |
| bool HttpStreamFactory::JobController::IsQuicAllowedForHost( |
| const std::string& host) { |
| const base::flat_set<std::string>& host_allowlist = |
| session_->params().quic_host_allowlist; |
| if (host_allowlist.empty()) |
| return true; |
| |
| std::string lowered_host = base::ToLowerASCII(host); |
| return base::Contains(host_allowlist, lowered_host); |
| } |
| |
| } // namespace net |