• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2024 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "net/quic/quic_session_pool_direct_job.h"
6 
7 #include "base/memory/weak_ptr.h"
8 #include "net/base/completion_once_callback.h"
9 #include "net/base/network_change_notifier.h"
10 #include "net/base/network_handle.h"
11 #include "net/base/request_priority.h"
12 #include "net/base/trace_constants.h"
13 #include "net/base/tracing.h"
14 #include "net/dns/host_resolver.h"
15 #include "net/dns/public/host_resolver_results.h"
16 #include "net/log/net_log_with_source.h"
17 #include "net/quic/address_utils.h"
18 #include "net/quic/quic_crypto_client_config_handle.h"
19 #include "net/quic/quic_http_stream.h"
20 #include "net/quic/quic_session_pool.h"
21 #include "net/spdy/multiplexed_session_creation_initiator.h"
22 #include "net/third_party/quiche/src/quiche/quic/core/quic_versions.h"
23 
24 namespace net {
25 
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,const NetLogWithSource & net_log)26 QuicSessionPool::DirectJob::DirectJob(
27     QuicSessionPool* pool,
28     quic::ParsedQuicVersion quic_version,
29     HostResolver* host_resolver,
30     QuicSessionAliasKey key,
31     std::unique_ptr<CryptoClientConfigHandle> client_config_handle,
32     bool retry_on_alternate_network_before_handshake,
33     RequestPriority priority,
34     bool use_dns_aliases,
35     bool require_dns_https_alpn,
36     int cert_verify_flags,
37     MultiplexedSessionCreationInitiator session_creation_initiator,
38     const NetLogWithSource& net_log)
39     : QuicSessionPool::Job::Job(
40           pool,
41           std::move(key),
42           std::move(client_config_handle),
43           priority,
44           NetLogWithSource::Make(
45               net_log.net_log(),
46               NetLogSourceType::QUIC_SESSION_POOL_DIRECT_JOB)),
47       quic_version_(std::move(quic_version)),
48       host_resolver_(host_resolver),
49       use_dns_aliases_(use_dns_aliases),
50       require_dns_https_alpn_(require_dns_https_alpn),
51       cert_verify_flags_(cert_verify_flags),
52       retry_on_alternate_network_before_handshake_(
53           retry_on_alternate_network_before_handshake),
54       session_creation_initiator_(session_creation_initiator) {
55   // TODO(davidben): `require_dns_https_alpn_` only exists to be `DCHECK`ed
56   // for consistency against `quic_version_`. Remove the parameter?
57   DCHECK_EQ(quic_version_.IsKnown(), !require_dns_https_alpn_);
58 }
59 
~DirectJob()60 QuicSessionPool::DirectJob::~DirectJob() {}
61 
Run(CompletionOnceCallback callback)62 int QuicSessionPool::DirectJob::Run(CompletionOnceCallback callback) {
63   int rv = DoLoop(OK);
64   if (rv == ERR_IO_PENDING) {
65     callback_ = std::move(callback);
66   }
67 
68   return rv > 0 ? OK : rv;
69 }
70 
SetRequestExpectations(QuicSessionRequest * request)71 void QuicSessionPool::DirectJob::SetRequestExpectations(
72     QuicSessionRequest* request) {
73   if (!host_resolution_finished_) {
74     request->ExpectOnHostResolution();
75   }
76   // Callers do not need to wait for OnQuicSessionCreationComplete if the
77   // kAsyncQuicSession flag is not set because session creation will be fully
78   // synchronous, so no need to call ExpectQuicSessionCreation.
79   const bool session_creation_finished =
80       session_attempt_ && session_attempt_->session_creation_finished();
81   if (base::FeatureList::IsEnabled(net::features::kAsyncQuicSession) &&
82       !session_creation_finished) {
83     request->ExpectQuicSessionCreation();
84   }
85 }
86 
UpdatePriority(RequestPriority old_priority,RequestPriority new_priority)87 void QuicSessionPool::DirectJob::UpdatePriority(RequestPriority old_priority,
88                                                 RequestPriority new_priority) {
89   if (old_priority == new_priority) {
90     return;
91   }
92 
93   if (resolve_host_request_ && !host_resolution_finished_) {
94     resolve_host_request_->ChangeRequestPriority(new_priority);
95   }
96 }
97 
PopulateNetErrorDetails(NetErrorDetails * details) const98 void QuicSessionPool::DirectJob::PopulateNetErrorDetails(
99     NetErrorDetails* details) const {
100   if (session_attempt_) {
101     session_attempt_->PopulateNetErrorDetails(details);
102   }
103 }
104 
DoLoop(int rv)105 int QuicSessionPool::DirectJob::DoLoop(int rv) {
106   TRACE_EVENT0(NetTracingCategory(), "QuicSessionPool::DirectJob::DoLoop");
107 
108   do {
109     IoState state = io_state_;
110     io_state_ = STATE_NONE;
111     switch (state) {
112       case STATE_RESOLVE_HOST:
113         CHECK_EQ(OK, rv);
114         rv = DoResolveHost();
115         break;
116       case STATE_RESOLVE_HOST_COMPLETE:
117         rv = DoResolveHostComplete(rv);
118         break;
119       case STATE_ATTEMPT_SESSION:
120         rv = DoAttemptSession();
121         break;
122       default:
123         NOTREACHED() << "io_state_: " << io_state_;
124     }
125   } while (io_state_ != STATE_NONE && rv != ERR_IO_PENDING);
126   return rv;
127 }
128 
DoResolveHost()129 int QuicSessionPool::DirectJob::DoResolveHost() {
130   dns_resolution_start_time_ = base::TimeTicks::Now();
131 
132   io_state_ = STATE_RESOLVE_HOST_COMPLETE;
133 
134   HostResolver::ResolveHostParameters parameters;
135   parameters.initial_priority = priority_;
136   parameters.secure_dns_policy = key_.session_key().secure_dns_policy();
137   resolve_host_request_ = host_resolver_->CreateRequest(
138       key_.destination(), key_.session_key().network_anonymization_key(),
139       net_log_, parameters);
140   // Unretained is safe because |this| owns the request, ensuring cancellation
141   // on destruction.
142   return resolve_host_request_->Start(
143       base::BindOnce(&QuicSessionPool::DirectJob::OnResolveHostComplete,
144                      base::Unretained(this)));
145 }
146 
DoResolveHostComplete(int rv)147 int QuicSessionPool::DirectJob::DoResolveHostComplete(int rv) {
148   host_resolution_finished_ = true;
149   dns_resolution_end_time_ = base::TimeTicks::Now();
150   if (rv != OK) {
151     return rv;
152   }
153 
154   DCHECK(!pool_->HasActiveSession(key_.session_key()));
155 
156   // Inform the pool of this resolution, which will set up
157   // a session alias, if possible.
158   const bool svcb_optional =
159       IsSvcbOptional(*resolve_host_request_->GetEndpointResults());
160   for (const auto& endpoint : *resolve_host_request_->GetEndpointResults()) {
161     // Only consider endpoints that would have been eligible for QUIC.
162     quic::ParsedQuicVersion endpoint_quic_version = pool_->SelectQuicVersion(
163         quic_version_, endpoint.metadata, svcb_optional);
164     if (!endpoint_quic_version.IsKnown()) {
165       continue;
166     }
167     if (pool_->HasMatchingIpSession(
168             key_, endpoint.ip_endpoints,
169             *resolve_host_request_->GetDnsAliasResults(), use_dns_aliases_)) {
170       LogConnectionIpPooling(true);
171       return OK;
172     }
173   }
174   io_state_ = STATE_ATTEMPT_SESSION;
175   return OK;
176 }
177 
DoAttemptSession()178 int QuicSessionPool::DirectJob::DoAttemptSession() {
179   // TODO(crbug.com/40256842): This logic only knows how to try one
180   // endpoint result.
181   bool svcb_optional =
182       IsSvcbOptional(*resolve_host_request_->GetEndpointResults());
183   bool found = false;
184   HostResolverEndpointResult endpoint_result;
185   quic::ParsedQuicVersion quic_version_used =
186       quic::ParsedQuicVersion::Unsupported();
187   for (const auto& candidate : *resolve_host_request_->GetEndpointResults()) {
188     quic::ParsedQuicVersion endpoint_quic_version = pool_->SelectQuicVersion(
189         quic_version_, candidate.metadata, svcb_optional);
190     if (endpoint_quic_version.IsKnown()) {
191       found = true;
192       quic_version_used = endpoint_quic_version;
193       endpoint_result = candidate;
194       break;
195     }
196   }
197   if (!found) {
198     return ERR_DNS_NO_MATCHING_SUPPORTED_ALPN;
199   }
200 
201   std::set<std::string> dns_aliases =
202       use_dns_aliases_ && resolve_host_request_->GetDnsAliasResults()
203           ? *resolve_host_request_->GetDnsAliasResults()
204           : std::set<std::string>();
205   // Passing an empty `crypto_client_config_handle` is safe because this job
206   // already owns a handle.
207   session_attempt_ = std::make_unique<QuicSessionAttempt>(
208       this, endpoint_result.ip_endpoints.front(), endpoint_result.metadata,
209       std::move(quic_version_used), cert_verify_flags_,
210       dns_resolution_start_time_, dns_resolution_end_time_,
211       retry_on_alternate_network_before_handshake_, use_dns_aliases_,
212       std::move(dns_aliases), /*crypto_client_config_handle=*/nullptr,
213       session_creation_initiator_);
214 
215   return session_attempt_->Start(
216       base::BindOnce(&DirectJob::OnSessionAttemptComplete, GetWeakPtr()));
217 }
218 
OnResolveHostComplete(int rv)219 void QuicSessionPool::DirectJob::OnResolveHostComplete(int rv) {
220   DCHECK(!host_resolution_finished_);
221   io_state_ = STATE_RESOLVE_HOST_COMPLETE;
222   rv = DoLoop(rv);
223 
224   for (QuicSessionRequest* request : requests()) {
225     request->OnHostResolutionComplete(rv, dns_resolution_start_time_,
226                                       dns_resolution_end_time_);
227   }
228 
229   if (rv != ERR_IO_PENDING && !callback_.is_null()) {
230     std::move(callback_).Run(rv);
231   }
232 }
233 
OnSessionAttemptComplete(int rv)234 void QuicSessionPool::DirectJob::OnSessionAttemptComplete(int rv) {
235   CHECK_NE(rv, ERR_IO_PENDING);
236   if (!callback_.is_null()) {
237     std::move(callback_).Run(rv);
238   }
239 }
240 
IsSvcbOptional(base::span<const HostResolverEndpointResult> results) const241 bool QuicSessionPool::DirectJob::IsSvcbOptional(
242     base::span<const HostResolverEndpointResult> results) const {
243   // If SVCB/HTTPS resolution succeeded, the client supports ECH, and all
244   // routes support ECH, disable the A/AAAA fallback. See Section 10.1 of
245   // draft-ietf-dnsop-svcb-https-11.
246   if (!pool_->ssl_config_service_->GetSSLContextConfig().ech_enabled) {
247     return true;  // ECH is not supported for this request.
248   }
249 
250   return !HostResolver::AllProtocolEndpointsHaveEch(results);
251 }
252 
253 }  // namespace net
254