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