• 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_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