xref: /aosp_15_r20/external/cronet/net/quic/quic_session_pool_session_attempt.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_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