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_attempt.h"
6
7 #include "base/auto_reset.h"
8 #include "base/feature_list.h"
9 #include "base/notreached.h"
10 #include "net/base/completion_once_callback.h"
11 #include "net/base/features.h"
12 #include "net/base/net_error_details.h"
13 #include "net/base/net_errors.h"
14 #include "net/dns/public/host_resolver_results.h"
15 #include "net/log/net_log_with_source.h"
16 #include "net/quic/address_utils.h"
17 #include "net/quic/quic_http_stream.h"
18 #include "net/quic/quic_session_pool.h"
19 #include "net/spdy/multiplexed_session_creation_initiator.h"
20 #include "net/third_party/quiche/src/quiche/quic/core/quic_versions.h"
21
22 namespace net {
23
24 namespace {
25
26 enum class JobProtocolErrorLocation {
27 kSessionStartReadingFailedAsync = 0,
28 kSessionStartReadingFailedSync = 1,
29 kCreateSessionFailedAsync = 2,
30 kCreateSessionFailedSync = 3,
31 kCryptoConnectFailedSync = 4,
32 kCryptoConnectFailedAsync = 5,
33 kMaxValue = kCryptoConnectFailedAsync,
34 };
35
HistogramProtocolErrorLocation(enum JobProtocolErrorLocation location)36 void HistogramProtocolErrorLocation(enum JobProtocolErrorLocation location) {
37 UMA_HISTOGRAM_ENUMERATION("Net.QuicStreamFactory.DoConnectFailureLocation",
38 location);
39 }
40
LogStaleConnectionTime(base::TimeTicks start_time)41 void LogStaleConnectionTime(base::TimeTicks start_time) {
42 UMA_HISTOGRAM_TIMES("Net.QuicSession.StaleConnectionTime",
43 base::TimeTicks::Now() - start_time);
44 }
45
LogValidConnectionTime(base::TimeTicks start_time)46 void LogValidConnectionTime(base::TimeTicks start_time) {
47 UMA_HISTOGRAM_TIMES("Net.QuicSession.ValidConnectionTime",
48 base::TimeTicks::Now() - start_time);
49 }
50
51 } // namespace
52
QuicSessionAttempt(Delegate * delegate,IPEndPoint ip_endpoint,ConnectionEndpointMetadata metadata,quic::ParsedQuicVersion quic_version,int cert_verify_flags,base::TimeTicks dns_resolution_start_time,base::TimeTicks dns_resolution_end_time,bool retry_on_alternate_network_before_handshake,bool use_dns_aliases,std::set<std::string> dns_aliases,std::unique_ptr<QuicCryptoClientConfigHandle> crypto_client_config_handle,MultiplexedSessionCreationInitiator session_creation_initiator)53 QuicSessionAttempt::QuicSessionAttempt(
54 Delegate* delegate,
55 IPEndPoint ip_endpoint,
56 ConnectionEndpointMetadata metadata,
57 quic::ParsedQuicVersion quic_version,
58 int cert_verify_flags,
59 base::TimeTicks dns_resolution_start_time,
60 base::TimeTicks dns_resolution_end_time,
61 bool retry_on_alternate_network_before_handshake,
62 bool use_dns_aliases,
63 std::set<std::string> dns_aliases,
64 std::unique_ptr<QuicCryptoClientConfigHandle> crypto_client_config_handle,
65 MultiplexedSessionCreationInitiator session_creation_initiator)
66 : delegate_(delegate),
67 ip_endpoint_(std::move(ip_endpoint)),
68 metadata_(std::move(metadata)),
69 quic_version_(std::move(quic_version)),
70 cert_verify_flags_(cert_verify_flags),
71 dns_resolution_start_time_(dns_resolution_start_time),
72 dns_resolution_end_time_(dns_resolution_end_time),
73 was_alternative_service_recently_broken_(
74 pool()->WasQuicRecentlyBroken(key().session_key())),
75 retry_on_alternate_network_before_handshake_(
76 retry_on_alternate_network_before_handshake),
77 use_dns_aliases_(use_dns_aliases),
78 dns_aliases_(std::move(dns_aliases)),
79 crypto_client_config_handle_(std::move(crypto_client_config_handle)),
80 session_creation_initiator_(session_creation_initiator) {
81 CHECK(delegate_);
82 DCHECK_NE(quic_version_, quic::ParsedQuicVersion::Unsupported());
83 }
84
QuicSessionAttempt(Delegate * delegate,IPEndPoint local_endpoint,IPEndPoint proxy_peer_endpoint,quic::ParsedQuicVersion quic_version,int cert_verify_flags,std::unique_ptr<QuicChromiumClientStream::Handle> proxy_stream,const HttpUserAgentSettings * http_user_agent_settings,MultiplexedSessionCreationInitiator session_creation_initiator)85 QuicSessionAttempt::QuicSessionAttempt(
86 Delegate* delegate,
87 IPEndPoint local_endpoint,
88 IPEndPoint proxy_peer_endpoint,
89 quic::ParsedQuicVersion quic_version,
90 int cert_verify_flags,
91 std::unique_ptr<QuicChromiumClientStream::Handle> proxy_stream,
92 const HttpUserAgentSettings* http_user_agent_settings,
93 MultiplexedSessionCreationInitiator session_creation_initiator)
94 : delegate_(delegate),
95 ip_endpoint_(std::move(proxy_peer_endpoint)),
96 quic_version_(std::move(quic_version)),
97 cert_verify_flags_(cert_verify_flags),
98 was_alternative_service_recently_broken_(
99 pool()->WasQuicRecentlyBroken(key().session_key())),
100 retry_on_alternate_network_before_handshake_(false),
101 use_dns_aliases_(false),
102 proxy_stream_(std::move(proxy_stream)),
103 http_user_agent_settings_(http_user_agent_settings),
104 local_endpoint_(std::move(local_endpoint)),
105 session_creation_initiator_(session_creation_initiator) {
106 CHECK(delegate_);
107 DCHECK_NE(quic_version_, quic::ParsedQuicVersion::Unsupported());
108 }
109
110 QuicSessionAttempt::~QuicSessionAttempt() = default;
111
Start(CompletionOnceCallback callback)112 int QuicSessionAttempt::Start(CompletionOnceCallback callback) {
113 CHECK_EQ(next_state_, State::kNone);
114
115 next_state_ = State::kCreateSession;
116 int rv = DoLoop(OK);
117 if (rv != ERR_IO_PENDING) {
118 return rv;
119 }
120
121 callback_ = std::move(callback);
122 return rv;
123 }
124
PopulateNetErrorDetails(NetErrorDetails * details) const125 void QuicSessionAttempt::PopulateNetErrorDetails(
126 NetErrorDetails* details) const {
127 if (session_) {
128 details->connection_info = QuicHttpStream::ConnectionInfoFromQuicVersion(
129 session_->connection()->version());
130 details->quic_connection_error = session_->error();
131 } else {
132 details->connection_info = connection_info_;
133 details->quic_connection_error = quic_connection_error_;
134 }
135 }
136
DoLoop(int rv)137 int QuicSessionAttempt::DoLoop(int rv) {
138 CHECK(!in_loop_);
139 CHECK_NE(next_state_, State::kNone);
140
141 base::AutoReset<bool> auto_reset(&in_loop_, true);
142 do {
143 State state = next_state_;
144 next_state_ = State::kNone;
145 switch (state) {
146 case State::kNone:
147 NOTREACHED() << "Invalid state";
148 case State::kCreateSession:
149 rv = DoCreateSession();
150 break;
151 case State::kCreateSessionComplete:
152 rv = DoCreateSessionComplete(rv);
153 break;
154 case State::kCryptoConnect:
155 rv = DoCryptoConnect(rv);
156 break;
157 case State::kConfirmConnection:
158 rv = DoConfirmConnection(rv);
159 break;
160 }
161 } while (next_state_ != State::kNone && rv != ERR_IO_PENDING);
162 return rv;
163 }
164
DoCreateSession()165 int QuicSessionAttempt::DoCreateSession() {
166 quic_connection_start_time_ = base::TimeTicks::Now();
167 next_state_ = State::kCreateSessionComplete;
168
169 const bool require_confirmation = was_alternative_service_recently_broken_;
170 net_log().AddEntryWithBoolParams(
171 NetLogEventType::QUIC_SESSION_POOL_JOB_CONNECT, NetLogEventPhase::BEGIN,
172 "require_confirmation", require_confirmation);
173
174 int rv;
175 if (proxy_stream_) {
176 std::string user_agent;
177 if (http_user_agent_settings_) {
178 user_agent = http_user_agent_settings_->GetUserAgent();
179 }
180 // Proxied connections are not on any specific network.
181 network_ = handles::kInvalidNetworkHandle;
182 rv = pool()->CreateSessionOnProxyStream(
183 base::BindOnce(&QuicSessionAttempt::OnCreateSessionComplete,
184 weak_ptr_factory_.GetWeakPtr()),
185 key(), quic_version_, cert_verify_flags_, require_confirmation,
186 std::move(local_endpoint_), std::move(ip_endpoint_),
187 std::move(proxy_stream_), std::move(user_agent), net_log(), network_);
188 } else {
189 if (base::FeatureList::IsEnabled(net::features::kAsyncQuicSession)) {
190 return pool()->CreateSessionAsync(
191 base::BindOnce(&QuicSessionAttempt::OnCreateSessionComplete,
192 weak_ptr_factory_.GetWeakPtr()),
193 key(), quic_version_, cert_verify_flags_, require_confirmation,
194 ip_endpoint_, metadata_, dns_resolution_start_time_,
195 dns_resolution_end_time_, net_log(), network_,
196 session_creation_initiator_);
197 }
198 rv = pool()->CreateSessionSync(
199 key(), quic_version_, cert_verify_flags_, require_confirmation,
200 ip_endpoint_, metadata_, dns_resolution_start_time_,
201 dns_resolution_end_time_, net_log(), &session_, &network_,
202 session_creation_initiator_);
203
204 DVLOG(1) << "Created session on network: " << network_;
205 }
206 if (rv == ERR_QUIC_PROTOCOL_ERROR) {
207 DCHECK(!session_);
208 HistogramProtocolErrorLocation(
209 JobProtocolErrorLocation::kCreateSessionFailedSync);
210 }
211 return rv;
212 }
213
DoCreateSessionComplete(int rv)214 int QuicSessionAttempt::DoCreateSessionComplete(int rv) {
215 session_creation_finished_ = true;
216 if (rv != OK) {
217 CHECK(!session_);
218 return rv;
219 }
220
221 next_state_ = State::kCryptoConnect;
222 if (!session_->connection()->connected()) {
223 return ERR_CONNECTION_CLOSED;
224 }
225
226 CHECK(session_);
227 session_->StartReading();
228 if (!session_->connection()->connected()) {
229 if (base::FeatureList::IsEnabled(net::features::kAsyncQuicSession)) {
230 HistogramProtocolErrorLocation(
231 JobProtocolErrorLocation::kSessionStartReadingFailedAsync);
232 } else {
233 HistogramProtocolErrorLocation(
234 JobProtocolErrorLocation::kSessionStartReadingFailedSync);
235 }
236 return ERR_QUIC_PROTOCOL_ERROR;
237 }
238 return OK;
239 }
240
DoCryptoConnect(int rv)241 int QuicSessionAttempt::DoCryptoConnect(int rv) {
242 if (rv != OK) {
243 // Reset `session_` to avoid dangling pointer.
244 ResetSession();
245 return rv;
246 }
247
248 DCHECK(session_);
249 next_state_ = State::kConfirmConnection;
250 rv = session_->CryptoConnect(
251 base::BindOnce(&QuicSessionAttempt::OnCryptoConnectComplete,
252 weak_ptr_factory_.GetWeakPtr()));
253
254 if (rv != ERR_IO_PENDING) {
255 LogValidConnectionTime(quic_connection_start_time_);
256 }
257
258 if (!session_->connection()->connected() &&
259 session_->error() == quic::QUIC_PROOF_INVALID) {
260 return ERR_QUIC_HANDSHAKE_FAILED;
261 }
262
263 if (rv == ERR_QUIC_PROTOCOL_ERROR) {
264 HistogramProtocolErrorLocation(
265 JobProtocolErrorLocation::kCryptoConnectFailedSync);
266 }
267
268 return rv;
269 }
270
DoConfirmConnection(int rv)271 int QuicSessionAttempt::DoConfirmConnection(int rv) {
272 UMA_HISTOGRAM_TIMES("Net.QuicSession.TimeFromResolveHostToConfirmConnection",
273 base::TimeTicks::Now() - dns_resolution_start_time_);
274 net_log().EndEvent(NetLogEventType::QUIC_SESSION_POOL_JOB_CONNECT);
275
276 if (was_alternative_service_recently_broken_) {
277 UMA_HISTOGRAM_BOOLEAN("Net.QuicSession.ConnectAfterBroken", rv == OK);
278 }
279
280 if (retry_on_alternate_network_before_handshake_ && session_ &&
281 !session_->OneRttKeysAvailable() &&
282 network_ == pool()->default_network()) {
283 if (session_->error() == quic::QUIC_NETWORK_IDLE_TIMEOUT ||
284 session_->error() == quic::QUIC_HANDSHAKE_TIMEOUT ||
285 session_->error() == quic::QUIC_PACKET_WRITE_ERROR) {
286 // Retry the connection on an alternate network if crypto handshake failed
287 // with network idle time out or handshake time out.
288 DCHECK(network_ != handles::kInvalidNetworkHandle);
289 network_ = pool()->FindAlternateNetwork(network_);
290 connection_retried_ = network_ != handles::kInvalidNetworkHandle;
291 UMA_HISTOGRAM_BOOLEAN(
292 "Net.QuicStreamFactory.AttemptMigrationBeforeHandshake",
293 connection_retried_);
294 UMA_HISTOGRAM_ENUMERATION(
295 "Net.QuicStreamFactory.AttemptMigrationBeforeHandshake."
296 "FailedConnectionType",
297 NetworkChangeNotifier::GetNetworkConnectionType(
298 pool()->default_network()),
299 NetworkChangeNotifier::ConnectionType::CONNECTION_LAST + 1);
300 if (connection_retried_) {
301 UMA_HISTOGRAM_ENUMERATION(
302 "Net.QuicStreamFactory.MigrationBeforeHandshake.NewConnectionType",
303 NetworkChangeNotifier::GetNetworkConnectionType(network_),
304 NetworkChangeNotifier::ConnectionType::CONNECTION_LAST + 1);
305 net_log().AddEvent(
306 NetLogEventType::QUIC_SESSION_POOL_JOB_RETRY_ON_ALTERNATE_NETWORK);
307 // Notify requests that connection on the default network failed.
308 delegate_->OnConnectionFailedOnDefaultNetwork();
309 DVLOG(1) << "Retry connection on alternate network: " << network_;
310 session_ = nullptr;
311 next_state_ = State::kCreateSession;
312 return OK;
313 }
314 }
315 }
316
317 if (connection_retried_) {
318 UMA_HISTOGRAM_BOOLEAN("Net.QuicStreamFactory.MigrationBeforeHandshake2",
319 rv == OK);
320 if (rv == OK) {
321 UMA_HISTOGRAM_BOOLEAN(
322 "Net.QuicStreamFactory.NetworkChangeDuringMigrationBeforeHandshake",
323 network_ == pool()->default_network());
324 } else {
325 base::UmaHistogramSparse(
326 "Net.QuicStreamFactory.MigrationBeforeHandshakeFailedReason", -rv);
327 }
328 } else if (network_ != handles::kInvalidNetworkHandle &&
329 network_ != pool()->default_network()) {
330 UMA_HISTOGRAM_BOOLEAN("Net.QuicStreamFactory.ConnectionOnNonDefaultNetwork",
331 rv == OK);
332 }
333
334 if (rv != OK) {
335 // Reset `session_` to avoid dangling pointer.
336 ResetSession();
337 return rv;
338 }
339
340 DCHECK(!pool()->HasActiveSession(key().session_key()));
341 // There may well now be an active session for this IP. If so, use the
342 // existing session instead.
343 if (pool()->HasMatchingIpSession(
344 key(), {ToIPEndPoint(session_->connection()->peer_address())},
345 /*aliases=*/{}, use_dns_aliases_)) {
346 QuicSessionPool::LogConnectionIpPooling(true);
347 session_->connection()->CloseConnection(
348 quic::QUIC_CONNECTION_IP_POOLED,
349 "An active session exists for the given IP.",
350 quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
351 session_ = nullptr;
352 return OK;
353 }
354 QuicSessionPool::LogConnectionIpPooling(false);
355
356 pool()->ActivateSession(
357 key(), session_,
358 use_dns_aliases_ ? std::move(dns_aliases_) : std::set<std::string>());
359
360 return OK;
361 }
362
OnCreateSessionComplete(base::expected<CreateSessionResult,int> result)363 void QuicSessionAttempt::OnCreateSessionComplete(
364 base::expected<CreateSessionResult, int> result) {
365 CHECK_EQ(next_state_, State::kCreateSessionComplete);
366 if (result.has_value()) {
367 session_ = result->session;
368 network_ = result->network;
369 DVLOG(1) << "Created session on network: " << network_;
370 } else {
371 if (result.error() == ERR_QUIC_PROTOCOL_ERROR) {
372 HistogramProtocolErrorLocation(
373 JobProtocolErrorLocation::kCreateSessionFailedAsync);
374 }
375 }
376
377 int rv = DoLoop(result.error_or(OK));
378
379 delegate_->OnQuicSessionCreationComplete(rv);
380
381 if (rv != ERR_IO_PENDING && !callback_.is_null()) {
382 std::move(callback_).Run(rv);
383 }
384 }
385
OnCryptoConnectComplete(int rv)386 void QuicSessionAttempt::OnCryptoConnectComplete(int rv) {
387 CHECK_EQ(next_state_, State::kConfirmConnection);
388
389 // This early return will be triggered when CloseSessionOnError is called
390 // before crypto handshake has completed.
391 if (!session_) {
392 LogStaleConnectionTime(quic_connection_start_time_);
393 return;
394 }
395
396 if (rv == ERR_QUIC_PROTOCOL_ERROR) {
397 HistogramProtocolErrorLocation(
398 JobProtocolErrorLocation::kCryptoConnectFailedAsync);
399 }
400
401 rv = DoLoop(rv);
402 if (rv != ERR_IO_PENDING && !callback_.is_null()) {
403 std::move(callback_).Run(rv);
404 }
405 }
406
ResetSession()407 void QuicSessionAttempt::ResetSession() {
408 CHECK(session_);
409 connection_info_ = QuicHttpStream::ConnectionInfoFromQuicVersion(
410 session_->connection()->version());
411 quic_connection_error_ = session_->error();
412 session_ = nullptr;
413 }
414
415 } // namespace net
416