| // Copyright 2024 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/quic/quic_session_pool_direct_job.h" |
| |
| #include "base/memory/weak_ptr.h" |
| #include "net/base/completion_once_callback.h" |
| #include "net/base/network_change_notifier.h" |
| #include "net/base/network_handle.h" |
| #include "net/base/request_priority.h" |
| #include "net/base/trace_constants.h" |
| #include "net/base/tracing.h" |
| #include "net/dns/host_resolver.h" |
| #include "net/dns/public/host_resolver_results.h" |
| #include "net/log/net_log_with_source.h" |
| #include "net/quic/address_utils.h" |
| #include "net/quic/quic_crypto_client_config_handle.h" |
| #include "net/quic/quic_http_stream.h" |
| #include "net/quic/quic_session_pool.h" |
| #include "net/spdy/multiplexed_session_creation_initiator.h" |
| #include "net/third_party/quiche/src/quiche/quic/core/quic_versions.h" |
| |
| namespace net { |
| |
| QuicSessionPool::DirectJob::DirectJob( |
| QuicSessionPool* pool, |
| quic::ParsedQuicVersion quic_version, |
| HostResolver* host_resolver, |
| QuicSessionAliasKey key, |
| std::unique_ptr<CryptoClientConfigHandle> client_config_handle, |
| bool retry_on_alternate_network_before_handshake, |
| RequestPriority priority, |
| bool use_dns_aliases, |
| bool require_dns_https_alpn, |
| int cert_verify_flags, |
| MultiplexedSessionCreationInitiator session_creation_initiator, |
| std::optional<ConnectionManagementConfig> connection_management_config, |
| const NetLogWithSource& net_log) |
| : QuicSessionPool::Job::Job( |
| pool, |
| std::move(key), |
| std::move(client_config_handle), |
| priority, |
| NetLogWithSource::Make( |
| net_log.net_log(), |
| NetLogSourceType::QUIC_SESSION_POOL_DIRECT_JOB)), |
| quic_version_(std::move(quic_version)), |
| host_resolver_(host_resolver), |
| use_dns_aliases_(use_dns_aliases), |
| require_dns_https_alpn_(require_dns_https_alpn), |
| cert_verify_flags_(cert_verify_flags), |
| retry_on_alternate_network_before_handshake_( |
| retry_on_alternate_network_before_handshake), |
| session_creation_initiator_(session_creation_initiator), |
| connection_management_config_(connection_management_config) { |
| // TODO(davidben): `require_dns_https_alpn_` only exists to be `DCHECK`ed |
| // for consistency against `quic_version_`. Remove the parameter? |
| DCHECK_EQ(quic_version_.IsKnown(), !require_dns_https_alpn_); |
| } |
| |
| QuicSessionPool::DirectJob::~DirectJob() {} |
| |
| int QuicSessionPool::DirectJob::Run(CompletionOnceCallback callback) { |
| int rv = DoLoop(OK); |
| if (rv == ERR_IO_PENDING) { |
| callback_ = std::move(callback); |
| } |
| |
| return rv > 0 ? OK : rv; |
| } |
| |
| void QuicSessionPool::DirectJob::SetRequestExpectations( |
| QuicSessionRequest* request) { |
| if (!host_resolution_finished_) { |
| request->ExpectOnHostResolution(); |
| } |
| // Callers do not need to wait for OnQuicSessionCreationComplete if the |
| // kAsyncQuicSession flag is not set because session creation will be fully |
| // synchronous, so no need to call ExpectQuicSessionCreation. |
| const bool session_creation_finished = |
| session_attempt_ && session_attempt_->session_creation_finished(); |
| if (base::FeatureList::IsEnabled(net::features::kAsyncQuicSession) && |
| !session_creation_finished) { |
| request->ExpectQuicSessionCreation(); |
| } |
| } |
| |
| void QuicSessionPool::DirectJob::UpdatePriority(RequestPriority old_priority, |
| RequestPriority new_priority) { |
| if (old_priority == new_priority) { |
| return; |
| } |
| |
| if (resolve_host_request_ && !host_resolution_finished_) { |
| resolve_host_request_->ChangeRequestPriority(new_priority); |
| } |
| } |
| |
| void QuicSessionPool::DirectJob::PopulateNetErrorDetails( |
| NetErrorDetails* details) const { |
| if (session_attempt_) { |
| session_attempt_->PopulateNetErrorDetails(details); |
| } |
| } |
| |
| int QuicSessionPool::DirectJob::DoLoop(int rv) { |
| TRACE_EVENT0(NetTracingCategory(), "QuicSessionPool::DirectJob::DoLoop"); |
| |
| do { |
| IoState state = io_state_; |
| io_state_ = STATE_NONE; |
| switch (state) { |
| case STATE_RESOLVE_HOST: |
| CHECK_EQ(OK, rv); |
| rv = DoResolveHost(); |
| break; |
| case STATE_RESOLVE_HOST_COMPLETE: |
| rv = DoResolveHostComplete(rv); |
| break; |
| case STATE_ATTEMPT_SESSION: |
| rv = DoAttemptSession(); |
| break; |
| default: |
| NOTREACHED() << "io_state_: " << io_state_; |
| } |
| } while (io_state_ != STATE_NONE && rv != ERR_IO_PENDING); |
| return rv; |
| } |
| |
| int QuicSessionPool::DirectJob::DoResolveHost() { |
| dns_resolution_start_time_ = base::TimeTicks::Now(); |
| |
| io_state_ = STATE_RESOLVE_HOST_COMPLETE; |
| |
| HostResolver::ResolveHostParameters parameters; |
| parameters.initial_priority = priority_; |
| parameters.secure_dns_policy = key_.session_key().secure_dns_policy(); |
| resolve_host_request_ = host_resolver_->CreateRequest( |
| key_.destination(), key_.session_key().network_anonymization_key(), |
| net_log_, parameters); |
| // Unretained is safe because |this| owns the request, ensuring cancellation |
| // on destruction. |
| return resolve_host_request_->Start( |
| base::BindOnce(&QuicSessionPool::DirectJob::OnResolveHostComplete, |
| base::Unretained(this))); |
| } |
| |
| int QuicSessionPool::DirectJob::DoResolveHostComplete(int rv) { |
| host_resolution_finished_ = true; |
| dns_resolution_end_time_ = base::TimeTicks::Now(); |
| if (rv != OK) { |
| return rv; |
| } |
| |
| DCHECK(!pool_->HasActiveSession(key_.session_key())); |
| |
| // Inform the pool of this resolution, which will set up |
| // a session alias, if possible. |
| const bool svcb_optional = |
| IsSvcbOptional(*resolve_host_request_->GetEndpointResults()); |
| for (const auto& endpoint : *resolve_host_request_->GetEndpointResults()) { |
| // Only consider endpoints that would have been eligible for QUIC. |
| quic::ParsedQuicVersion endpoint_quic_version = pool_->SelectQuicVersion( |
| quic_version_, endpoint.metadata, svcb_optional); |
| if (!endpoint_quic_version.IsKnown()) { |
| continue; |
| } |
| if (pool_->HasMatchingIpSession( |
| key_, endpoint.ip_endpoints, |
| *resolve_host_request_->GetDnsAliasResults(), use_dns_aliases_)) { |
| LogConnectionIpPooling(true); |
| return OK; |
| } |
| } |
| io_state_ = STATE_ATTEMPT_SESSION; |
| return OK; |
| } |
| |
| int QuicSessionPool::DirectJob::DoAttemptSession() { |
| // TODO(crbug.com/40256842): This logic only knows how to try one |
| // endpoint result. |
| bool svcb_optional = |
| IsSvcbOptional(*resolve_host_request_->GetEndpointResults()); |
| bool found = false; |
| HostResolverEndpointResult endpoint_result; |
| quic::ParsedQuicVersion quic_version_used = |
| quic::ParsedQuicVersion::Unsupported(); |
| for (const auto& candidate : *resolve_host_request_->GetEndpointResults()) { |
| quic::ParsedQuicVersion endpoint_quic_version = pool_->SelectQuicVersion( |
| quic_version_, candidate.metadata, svcb_optional); |
| if (endpoint_quic_version.IsKnown()) { |
| found = true; |
| quic_version_used = endpoint_quic_version; |
| endpoint_result = candidate; |
| break; |
| } |
| } |
| if (!found) { |
| return ERR_DNS_NO_MATCHING_SUPPORTED_ALPN; |
| } |
| |
| std::set<std::string> dns_aliases = |
| use_dns_aliases_ && resolve_host_request_->GetDnsAliasResults() |
| ? *resolve_host_request_->GetDnsAliasResults() |
| : std::set<std::string>(); |
| // Passing an empty `crypto_client_config_handle` is safe because this job |
| // already owns a handle. |
| session_attempt_ = std::make_unique<QuicSessionAttempt>( |
| this, endpoint_result.ip_endpoints.front(), endpoint_result.metadata, |
| std::move(quic_version_used), cert_verify_flags_, |
| dns_resolution_start_time_, dns_resolution_end_time_, |
| retry_on_alternate_network_before_handshake_, use_dns_aliases_, |
| std::move(dns_aliases), /*crypto_client_config_handle=*/nullptr, |
| session_creation_initiator_, connection_management_config_); |
| |
| return session_attempt_->Start( |
| base::BindOnce(&DirectJob::OnSessionAttemptComplete, GetWeakPtr())); |
| } |
| |
| void QuicSessionPool::DirectJob::OnResolveHostComplete(int rv) { |
| DCHECK(!host_resolution_finished_); |
| io_state_ = STATE_RESOLVE_HOST_COMPLETE; |
| rv = DoLoop(rv); |
| |
| for (QuicSessionRequest* request : requests()) { |
| request->OnHostResolutionComplete(rv, dns_resolution_start_time_, |
| dns_resolution_end_time_); |
| } |
| |
| if (rv != ERR_IO_PENDING && !callback_.is_null()) { |
| std::move(callback_).Run(rv); |
| } |
| } |
| |
| void QuicSessionPool::DirectJob::OnSessionAttemptComplete(int rv) { |
| CHECK_NE(rv, ERR_IO_PENDING); |
| if (!callback_.is_null()) { |
| std::move(callback_).Run(rv); |
| } |
| } |
| |
| bool QuicSessionPool::DirectJob::IsSvcbOptional( |
| base::span<const HostResolverEndpointResult> results) const { |
| // If SVCB/HTTPS resolution succeeded, the client supports ECH, and all |
| // routes support ECH, disable the A/AAAA fallback. See Section 10.1 of |
| // draft-ietf-dnsop-svcb-https-11. |
| if (!pool_->ssl_config_service_->GetSSLContextConfig().ech_enabled) { |
| return true; // ECH is not supported for this request. |
| } |
| |
| return !HostResolver::AllProtocolEndpointsHaveEch(results); |
| } |
| |
| } // namespace net |