xref: /aosp_15_r20/external/cronet/net/quic/quic_session_pool_direct_job.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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/third_party/quiche/src/quiche/quic/core/quic_versions.h"
22 
23 namespace net {
24 
DirectJob(QuicSessionPool * pool,quic::ParsedQuicVersion quic_version,HostResolver * host_resolver,const 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,const NetLogWithSource & net_log)25 QuicSessionPool::DirectJob::DirectJob(
26     QuicSessionPool* pool,
27     quic::ParsedQuicVersion quic_version,
28     HostResolver* host_resolver,
29     const QuicSessionAliasKey& key,
30     std::unique_ptr<CryptoClientConfigHandle> client_config_handle,
31     bool retry_on_alternate_network_before_handshake,
32     RequestPriority priority,
33     bool use_dns_aliases,
34     bool require_dns_https_alpn,
35     int cert_verify_flags,
36     const NetLogWithSource& net_log)
37     : QuicSessionPool::Job::Job(
38           pool,
39           key,
40           std::move(client_config_handle),
41           priority,
42           NetLogWithSource::Make(
43               net_log.net_log(),
44               NetLogSourceType::QUIC_SESSION_POOL_DIRECT_JOB)),
45       quic_version_(std::move(quic_version)),
46       host_resolver_(host_resolver),
47       use_dns_aliases_(use_dns_aliases),
48       require_dns_https_alpn_(require_dns_https_alpn),
49       cert_verify_flags_(cert_verify_flags),
50       retry_on_alternate_network_before_handshake_(
51           retry_on_alternate_network_before_handshake) {
52   // TODO(davidben): `require_dns_https_alpn_` only exists to be `DCHECK`ed
53   // for consistency against `quic_version_`. Remove the parameter?
54   DCHECK_EQ(quic_version_.IsKnown(), !require_dns_https_alpn_);
55 }
56 
~DirectJob()57 QuicSessionPool::DirectJob::~DirectJob() {}
58 
Run(CompletionOnceCallback callback)59 int QuicSessionPool::DirectJob::Run(CompletionOnceCallback callback) {
60   int rv = DoLoop(OK);
61   if (rv == ERR_IO_PENDING) {
62     callback_ = std::move(callback);
63   }
64 
65   return rv > 0 ? OK : rv;
66 }
67 
SetRequestExpectations(QuicSessionRequest * request)68 void QuicSessionPool::DirectJob::SetRequestExpectations(
69     QuicSessionRequest* request) {
70   if (!host_resolution_finished_) {
71     request->ExpectOnHostResolution();
72   }
73   // Callers do not need to wait for OnQuicSessionCreationComplete if the
74   // kAsyncQuicSession flag is not set because session creation will be fully
75   // synchronous, so no need to call ExpectQuicSessionCreation.
76   const bool session_creation_finished =
77       session_attempt_ && session_attempt_->session_creation_finished();
78   if (base::FeatureList::IsEnabled(net::features::kAsyncQuicSession) &&
79       !session_creation_finished) {
80     request->ExpectQuicSessionCreation();
81   }
82 }
83 
UpdatePriority(RequestPriority old_priority,RequestPriority new_priority)84 void QuicSessionPool::DirectJob::UpdatePriority(RequestPriority old_priority,
85                                                 RequestPriority new_priority) {
86   if (old_priority == new_priority) {
87     return;
88   }
89 
90   if (resolve_host_request_ && !host_resolution_finished_) {
91     resolve_host_request_->ChangeRequestPriority(new_priority);
92   }
93 }
94 
PopulateNetErrorDetails(NetErrorDetails * details) const95 void QuicSessionPool::DirectJob::PopulateNetErrorDetails(
96     NetErrorDetails* details) const {
97   if (!session_attempt_ || !session_attempt_->session()) {
98     return;
99   }
100   details->connection_info = QuicHttpStream::ConnectionInfoFromQuicVersion(
101       session_attempt_->session()->connection()->version());
102   details->quic_connection_error = session_attempt_->session()->error();
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         break;
125     }
126   } while (io_state_ != STATE_NONE && rv != ERR_IO_PENDING);
127   return rv;
128 }
129 
DoResolveHost()130 int QuicSessionPool::DirectJob::DoResolveHost() {
131   dns_resolution_start_time_ = base::TimeTicks::Now();
132 
133   io_state_ = STATE_RESOLVE_HOST_COMPLETE;
134 
135   HostResolver::ResolveHostParameters parameters;
136   parameters.initial_priority = priority_;
137   parameters.secure_dns_policy = key_.session_key().secure_dns_policy();
138   resolve_host_request_ = host_resolver_->CreateRequest(
139       key_.destination(), key_.session_key().network_anonymization_key(),
140       net_log_, parameters);
141   // Unretained is safe because |this| owns the request, ensuring cancellation
142   // on destruction.
143   return resolve_host_request_->Start(
144       base::BindOnce(&QuicSessionPool::DirectJob::OnResolveHostComplete,
145                      base::Unretained(this)));
146 }
147 
DoResolveHostComplete(int rv)148 int QuicSessionPool::DirectJob::DoResolveHostComplete(int rv) {
149   host_resolution_finished_ = true;
150   dns_resolution_end_time_ = base::TimeTicks::Now();
151   if (rv != OK) {
152     return rv;
153   }
154 
155   DCHECK(!pool_->HasActiveSession(key_.session_key()));
156 
157   // Inform the pool of this resolution, which will set up
158   // a session alias, if possible.
159   const bool svcb_optional =
160       IsSvcbOptional(*resolve_host_request_->GetEndpointResults());
161   for (const auto& endpoint : *resolve_host_request_->GetEndpointResults()) {
162     // Only consider endpoints that would have been eligible for QUIC.
163     quic::ParsedQuicVersion endpoint_quic_version = pool_->SelectQuicVersion(
164         quic_version_, endpoint.metadata, svcb_optional);
165     if (!endpoint_quic_version.IsKnown()) {
166       continue;
167     }
168     if (pool_->HasMatchingIpSession(
169             key_, endpoint.ip_endpoints,
170             *resolve_host_request_->GetDnsAliasResults(), use_dns_aliases_)) {
171       LogConnectionIpPooling(true);
172       return OK;
173     }
174   }
175   io_state_ = STATE_ATTEMPT_SESSION;
176   return OK;
177 }
178 
DoAttemptSession()179 int QuicSessionPool::DirectJob::DoAttemptSession() {
180   // TODO(https://crbug.com/1416409): This logic only knows how to try one
181   // endpoint result.
182   bool svcb_optional =
183       IsSvcbOptional(*resolve_host_request_->GetEndpointResults());
184   bool found = false;
185   HostResolverEndpointResult endpoint_result;
186   quic::ParsedQuicVersion quic_version_used =
187       quic::ParsedQuicVersion::Unsupported();
188   for (const auto& candidate : *resolve_host_request_->GetEndpointResults()) {
189     quic::ParsedQuicVersion endpoint_quic_version = pool_->SelectQuicVersion(
190         quic_version_, candidate.metadata, svcb_optional);
191     if (endpoint_quic_version.IsKnown()) {
192       found = true;
193       quic_version_used = endpoint_quic_version;
194       endpoint_result = candidate;
195       break;
196     }
197   }
198   if (!found) {
199     return ERR_DNS_NO_MATCHING_SUPPORTED_ALPN;
200   }
201 
202   std::set<std::string> dns_aliases =
203       use_dns_aliases_ && resolve_host_request_->GetDnsAliasResults()
204           ? *resolve_host_request_->GetDnsAliasResults()
205           : std::set<std::string>();
206   session_attempt_ = std::make_unique<SessionAttempt>(
207       this, endpoint_result.ip_endpoints.front(), endpoint_result.metadata,
208       std::move(quic_version_used), cert_verify_flags_,
209       dns_resolution_start_time_, dns_resolution_end_time_,
210       retry_on_alternate_network_before_handshake_, use_dns_aliases_,
211       std::move(dns_aliases));
212 
213   return session_attempt_->Start(
214       base::BindOnce(&DirectJob::OnSessionAttemptComplete, GetWeakPtr()));
215 }
216 
OnResolveHostComplete(int rv)217 void QuicSessionPool::DirectJob::OnResolveHostComplete(int rv) {
218   DCHECK(!host_resolution_finished_);
219   io_state_ = STATE_RESOLVE_HOST_COMPLETE;
220   rv = DoLoop(rv);
221 
222   for (QuicSessionRequest* request : requests()) {
223     request->OnHostResolutionComplete(rv);
224   }
225 
226   if (rv != ERR_IO_PENDING && !callback_.is_null()) {
227     std::move(callback_).Run(rv);
228   }
229 }
230 
OnSessionAttemptComplete(int rv)231 void QuicSessionPool::DirectJob::OnSessionAttemptComplete(int rv) {
232   CHECK_NE(rv, ERR_IO_PENDING);
233   if (!callback_.is_null()) {
234     std::move(callback_).Run(rv);
235   }
236 }
237 
IsSvcbOptional(base::span<const HostResolverEndpointResult> results) const238 bool QuicSessionPool::DirectJob::IsSvcbOptional(
239     base::span<const HostResolverEndpointResult> results) const {
240   // If SVCB/HTTPS resolution succeeded, the client supports ECH, and all
241   // routes support ECH, disable the A/AAAA fallback. See Section 10.1 of
242   // draft-ietf-dnsop-svcb-https-11.
243   if (!pool_->ssl_config_service_->GetSSLContextConfig().ech_enabled) {
244     return true;  // ECH is not supported for this request.
245   }
246 
247   return !HostResolver::AllProtocolEndpointsHaveEch(results);
248 }
249 
250 }  // namespace net
251