xref: /aosp_15_r20/external/cronet/net/spdy/spdy_session_pool.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2012 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/spdy/spdy_session_pool.h"
6 
7 #include <set>
8 #include <utility>
9 
10 #include "base/check_op.h"
11 #include "base/containers/contains.h"
12 #include "base/functional/bind.h"
13 #include "base/metrics/histogram_macros.h"
14 #include "base/ranges/algorithm.h"
15 #include "base/task/single_thread_task_runner.h"
16 #include "base/values.h"
17 #include "build/build_config.h"
18 #include "net/base/ip_endpoint.h"
19 #include "net/base/trace_constants.h"
20 #include "net/base/tracing.h"
21 #include "net/dns/host_resolver.h"
22 #include "net/dns/public/host_resolver_source.h"
23 #include "net/http/http_network_session.h"
24 #include "net/http/http_server_properties.h"
25 #include "net/http/http_stream_request.h"
26 #include "net/log/net_log_event_type.h"
27 #include "net/log/net_log_source.h"
28 #include "net/log/net_log_with_source.h"
29 #include "net/socket/client_socket_handle.h"
30 #include "net/spdy/spdy_session.h"
31 #include "net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_constants.h"
32 #include "net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_static_table.h"
33 
34 namespace net {
35 
36 namespace {
37 
38 enum SpdySessionGetTypes {
39   CREATED_NEW                 = 0,
40   FOUND_EXISTING              = 1,
41   FOUND_EXISTING_FROM_IP_POOL = 2,
42   IMPORTED_FROM_SOCKET        = 3,
43   SPDY_SESSION_GET_MAX        = 4
44 };
45 
46 }  // namespace
47 
48 SpdySessionPool::SpdySessionRequest::Delegate::Delegate() = default;
49 SpdySessionPool::SpdySessionRequest::Delegate::~Delegate() = default;
50 
SpdySessionRequest(const SpdySessionKey & key,bool enable_ip_based_pooling,bool is_websocket,bool is_blocking_request_for_session,Delegate * delegate,SpdySessionPool * spdy_session_pool)51 SpdySessionPool::SpdySessionRequest::SpdySessionRequest(
52     const SpdySessionKey& key,
53     bool enable_ip_based_pooling,
54     bool is_websocket,
55     bool is_blocking_request_for_session,
56     Delegate* delegate,
57     SpdySessionPool* spdy_session_pool)
58     : key_(key),
59       enable_ip_based_pooling_(enable_ip_based_pooling),
60       is_websocket_(is_websocket),
61       is_blocking_request_for_session_(is_blocking_request_for_session),
62       delegate_(delegate),
63       spdy_session_pool_(spdy_session_pool) {}
64 
~SpdySessionRequest()65 SpdySessionPool::SpdySessionRequest::~SpdySessionRequest() {
66   if (spdy_session_pool_)
67     spdy_session_pool_->RemoveRequestForSpdySession(this);
68 }
69 
OnRemovedFromPool()70 void SpdySessionPool::SpdySessionRequest::OnRemovedFromPool() {
71   DCHECK(spdy_session_pool_);
72   spdy_session_pool_ = nullptr;
73 }
74 
SpdySessionPool(HostResolver * resolver,SSLClientContext * ssl_client_context,HttpServerProperties * http_server_properties,TransportSecurityState * transport_security_state,const quic::ParsedQuicVersionVector & quic_supported_versions,bool enable_ping_based_connection_checking,bool is_http2_enabled,bool is_quic_enabled,size_t session_max_recv_window_size,int session_max_queued_capped_frames,const spdy::SettingsMap & initial_settings,bool enable_http2_settings_grease,const std::optional<GreasedHttp2Frame> & greased_http2_frame,bool http2_end_stream_with_data_frame,bool enable_priority_update,bool go_away_on_ip_change,SpdySessionPool::TimeFunc time_func,NetworkQualityEstimator * network_quality_estimator,bool cleanup_sessions_on_ip_address_changed)75 SpdySessionPool::SpdySessionPool(
76     HostResolver* resolver,
77     SSLClientContext* ssl_client_context,
78     HttpServerProperties* http_server_properties,
79     TransportSecurityState* transport_security_state,
80     const quic::ParsedQuicVersionVector& quic_supported_versions,
81     bool enable_ping_based_connection_checking,
82     bool is_http2_enabled,
83     bool is_quic_enabled,
84     size_t session_max_recv_window_size,
85     int session_max_queued_capped_frames,
86     const spdy::SettingsMap& initial_settings,
87     bool enable_http2_settings_grease,
88     const std::optional<GreasedHttp2Frame>& greased_http2_frame,
89     bool http2_end_stream_with_data_frame,
90     bool enable_priority_update,
91     bool go_away_on_ip_change,
92     SpdySessionPool::TimeFunc time_func,
93     NetworkQualityEstimator* network_quality_estimator,
94     bool cleanup_sessions_on_ip_address_changed)
95     : http_server_properties_(http_server_properties),
96       transport_security_state_(transport_security_state),
97       ssl_client_context_(ssl_client_context),
98       resolver_(resolver),
99       quic_supported_versions_(quic_supported_versions),
100       enable_ping_based_connection_checking_(
101           enable_ping_based_connection_checking),
102       is_http2_enabled_(is_http2_enabled),
103       is_quic_enabled_(is_quic_enabled),
104       session_max_recv_window_size_(session_max_recv_window_size),
105       session_max_queued_capped_frames_(session_max_queued_capped_frames),
106       initial_settings_(initial_settings),
107       enable_http2_settings_grease_(enable_http2_settings_grease),
108       greased_http2_frame_(greased_http2_frame),
109       http2_end_stream_with_data_frame_(http2_end_stream_with_data_frame),
110       enable_priority_update_(enable_priority_update),
111       go_away_on_ip_change_(go_away_on_ip_change),
112       time_func_(time_func),
113       network_quality_estimator_(network_quality_estimator),
114       cleanup_sessions_on_ip_address_changed_(
115           cleanup_sessions_on_ip_address_changed) {
116   if (cleanup_sessions_on_ip_address_changed_)
117     NetworkChangeNotifier::AddIPAddressObserver(this);
118   if (ssl_client_context_)
119     ssl_client_context_->AddObserver(this);
120 }
121 
~SpdySessionPool()122 SpdySessionPool::~SpdySessionPool() {
123 #if DCHECK_IS_ON()
124   for (const auto& request_info : spdy_session_request_map_) {
125     // The should be no pending SpdySessionRequests on destruction, though there
126     // may be callbacks waiting to be invoked, since they use weak pointers and
127     // there's no API to unregister them.
128     DCHECK(request_info.second.request_set.empty());
129   }
130 #endif  // DCHECK_IS_ON()
131 
132   // TODO(bnc): CloseAllSessions() is also called in HttpNetworkSession
133   // destructor, one of the two calls should be removed.
134   CloseAllSessions();
135 
136   while (!sessions_.empty()) {
137     // Destroy sessions to enforce that lifetime is scoped to SpdySessionPool.
138     // Write callbacks queued upon session drain are not invoked.
139     RemoveUnavailableSession((*sessions_.begin())->GetWeakPtr());
140   }
141 
142   if (ssl_client_context_)
143     ssl_client_context_->RemoveObserver(this);
144   if (cleanup_sessions_on_ip_address_changed_)
145     NetworkChangeNotifier::RemoveIPAddressObserver(this);
146 }
147 
CreateAvailableSessionFromSocketHandle(const SpdySessionKey & key,std::unique_ptr<ClientSocketHandle> client_socket_handle,const NetLogWithSource & net_log,base::WeakPtr<SpdySession> * session)148 int SpdySessionPool::CreateAvailableSessionFromSocketHandle(
149     const SpdySessionKey& key,
150     std::unique_ptr<ClientSocketHandle> client_socket_handle,
151     const NetLogWithSource& net_log,
152     base::WeakPtr<SpdySession>* session) {
153   TRACE_EVENT0(NetTracingCategory(),
154                "SpdySessionPool::CreateAvailableSessionFromSocketHandle");
155 
156   std::unique_ptr<SpdySession> new_session =
157       CreateSession(key, net_log.net_log());
158   std::set<std::string> dns_aliases =
159       client_socket_handle->socket()->GetDnsAliases();
160 
161   new_session->InitializeWithSocketHandle(std::move(client_socket_handle),
162                                           this);
163   *session = InsertSession(key, std::move(new_session), net_log,
164                            std::move(dns_aliases));
165 
166   if (!(*session)->HasAcceptableTransportSecurity()) {
167     (*session)->CloseSessionOnError(ERR_HTTP2_INADEQUATE_TRANSPORT_SECURITY,
168                                     "");
169     return ERR_HTTP2_INADEQUATE_TRANSPORT_SECURITY;
170   }
171 
172   int rv = (*session)->ParseAlps();
173   if (rv != OK) {
174     DCHECK_NE(ERR_IO_PENDING, rv);
175     // ParseAlps() already closed the connection on error.
176     return rv;
177   }
178 
179   return OK;
180 }
181 
CreateAvailableSessionFromSocket(const SpdySessionKey & key,std::unique_ptr<StreamSocket> socket_stream,const LoadTimingInfo::ConnectTiming & connect_timing,const NetLogWithSource & net_log)182 base::WeakPtr<SpdySession> SpdySessionPool::CreateAvailableSessionFromSocket(
183     const SpdySessionKey& key,
184     std::unique_ptr<StreamSocket> socket_stream,
185     const LoadTimingInfo::ConnectTiming& connect_timing,
186     const NetLogWithSource& net_log) {
187   TRACE_EVENT0(NetTracingCategory(),
188                "SpdySessionPool::CreateAvailableSessionFromSocket");
189 
190   std::unique_ptr<SpdySession> new_session =
191       CreateSession(key, net_log.net_log());
192   std::set<std::string> dns_aliases = socket_stream->GetDnsAliases();
193 
194   new_session->InitializeWithSocket(std::move(socket_stream), connect_timing,
195                                     this);
196 
197   return InsertSession(key, std::move(new_session), net_log,
198                        std::move(dns_aliases));
199 }
200 
FindAvailableSession(const SpdySessionKey & key,bool enable_ip_based_pooling,bool is_websocket,const NetLogWithSource & net_log)201 base::WeakPtr<SpdySession> SpdySessionPool::FindAvailableSession(
202     const SpdySessionKey& key,
203     bool enable_ip_based_pooling,
204     bool is_websocket,
205     const NetLogWithSource& net_log) {
206   auto it = LookupAvailableSessionByKey(key);
207   if (it == available_sessions_.end() ||
208       (is_websocket && !it->second->support_websocket())) {
209     return base::WeakPtr<SpdySession>();
210   }
211 
212   if (key == it->second->spdy_session_key()) {
213     UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", FOUND_EXISTING,
214                               SPDY_SESSION_GET_MAX);
215     net_log.AddEventReferencingSource(
216         NetLogEventType::HTTP2_SESSION_POOL_FOUND_EXISTING_SESSION,
217         it->second->net_log().source());
218     return it->second;
219   }
220 
221   if (enable_ip_based_pooling) {
222     UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", FOUND_EXISTING_FROM_IP_POOL,
223                               SPDY_SESSION_GET_MAX);
224     net_log.AddEventReferencingSource(
225         NetLogEventType::HTTP2_SESSION_POOL_FOUND_EXISTING_SESSION_FROM_IP_POOL,
226         it->second->net_log().source());
227     return it->second;
228   }
229 
230   return base::WeakPtr<SpdySession>();
231 }
232 
HasAvailableSession(const SpdySessionKey & key,bool is_websocket) const233 bool SpdySessionPool::HasAvailableSession(const SpdySessionKey& key,
234                                           bool is_websocket) const {
235   const auto it = available_sessions_.find(key);
236   return it != available_sessions_.end() &&
237          (!is_websocket || it->second->support_websocket());
238 }
239 
RequestSession(const SpdySessionKey & key,bool enable_ip_based_pooling,bool is_websocket,const NetLogWithSource & net_log,base::RepeatingClosure on_blocking_request_destroyed_callback,SpdySessionRequest::Delegate * delegate,std::unique_ptr<SpdySessionRequest> * spdy_session_request,bool * is_blocking_request_for_session)240 base::WeakPtr<SpdySession> SpdySessionPool::RequestSession(
241     const SpdySessionKey& key,
242     bool enable_ip_based_pooling,
243     bool is_websocket,
244     const NetLogWithSource& net_log,
245     base::RepeatingClosure on_blocking_request_destroyed_callback,
246     SpdySessionRequest::Delegate* delegate,
247     std::unique_ptr<SpdySessionRequest>* spdy_session_request,
248     bool* is_blocking_request_for_session) {
249   DCHECK(delegate);
250 
251   base::WeakPtr<SpdySession> spdy_session =
252       FindAvailableSession(key, enable_ip_based_pooling, is_websocket, net_log);
253   if (spdy_session) {
254     // This value doesn't really matter, but best to always populate it, for
255     // consistency.
256     *is_blocking_request_for_session = true;
257     return spdy_session;
258   }
259 
260   RequestInfoForKey* request_info = &spdy_session_request_map_[key];
261   *is_blocking_request_for_session = !request_info->has_blocking_request;
262   *spdy_session_request = std::make_unique<SpdySessionRequest>(
263       key, enable_ip_based_pooling, is_websocket,
264       *is_blocking_request_for_session, delegate, this);
265   request_info->request_set.insert(spdy_session_request->get());
266 
267   if (*is_blocking_request_for_session) {
268     request_info->has_blocking_request = true;
269   } else if (on_blocking_request_destroyed_callback) {
270     request_info->deferred_callbacks.push_back(
271         on_blocking_request_destroyed_callback);
272   }
273   return nullptr;
274 }
275 
OnHostResolutionComplete(const SpdySessionKey & key,bool is_websocket,const std::vector<HostResolverEndpointResult> & endpoint_results,const std::set<std::string> & aliases)276 OnHostResolutionCallbackResult SpdySessionPool::OnHostResolutionComplete(
277     const SpdySessionKey& key,
278     bool is_websocket,
279     const std::vector<HostResolverEndpointResult>& endpoint_results,
280     const std::set<std::string>& aliases) {
281   // If there are no pending requests for that alias, nothing to do.
282   if (spdy_session_request_map_.find(key) == spdy_session_request_map_.end())
283     return OnHostResolutionCallbackResult::kContinue;
284 
285   // Check if there's already a matching session. If so, there may already
286   // be a pending task to inform consumers of the alias. In this case, do
287   // nothing, but inform the caller to wait for such a task to run.
288   auto existing_session_it = LookupAvailableSessionByKey(key);
289   if (existing_session_it != available_sessions_.end()) {
290     if (is_websocket && !existing_session_it->second->support_websocket()) {
291       // We don't look for aliased sessions because it would not be possible to
292       // add them to the available_sessions_ map. See https://crbug.com/1220771.
293       return OnHostResolutionCallbackResult::kContinue;
294     }
295 
296     return OnHostResolutionCallbackResult::kMayBeDeletedAsync;
297   }
298 
299   for (const auto& endpoint : endpoint_results) {
300     // If `endpoint` has no associated ALPN protocols, it is TCP-based and thus
301     // would have been eligible for connecting with HTTP/2.
302     if (!endpoint.metadata.supported_protocol_alpns.empty() &&
303         !base::Contains(endpoint.metadata.supported_protocol_alpns, "h2")) {
304       continue;
305     }
306     for (const auto& address : endpoint.ip_endpoints) {
307       auto range = aliases_.equal_range(address);
308       for (auto alias_it = range.first; alias_it != range.second; ++alias_it) {
309         // We found a potential alias.
310         const SpdySessionKey& alias_key = alias_it->second;
311 
312         auto available_session_it = LookupAvailableSessionByKey(alias_key);
313         // It shouldn't be in the aliases table if it doesn't exist!
314         DCHECK(available_session_it != available_sessions_.end());
315 
316         SpdySessionKey::CompareForAliasingResult compare_result =
317             alias_key.CompareForAliasing(key);
318         // Keys must be aliasable.
319         if (!compare_result.is_potentially_aliasable) {
320           continue;
321         }
322 
323         if (is_websocket &&
324             !available_session_it->second->support_websocket()) {
325           continue;
326         }
327 
328         // Make copy of WeakPtr as call to UnmapKey() will delete original.
329         const base::WeakPtr<SpdySession> available_session =
330             available_session_it->second;
331 
332         // Need to verify that the server is authenticated to serve traffic for
333         // |host_port_proxy_pair| too.
334         if (!available_session->VerifyDomainAuthentication(
335                 key.host_port_pair().host())) {
336           UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 0, 2);
337           continue;
338         }
339 
340         UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 1, 2);
341 
342         bool adding_pooled_alias = true;
343 
344         // If socket tags differ, see if session's socket tag can be changed.
345         if (!compare_result.is_socket_tag_match) {
346           SpdySessionKey old_key = available_session->spdy_session_key();
347           SpdySessionKey new_key(
348               old_key.host_port_pair(), old_key.privacy_mode(),
349               old_key.proxy_chain(), old_key.session_usage(), key.socket_tag(),
350               old_key.network_anonymization_key(), old_key.secure_dns_policy(),
351               old_key.disable_cert_verification_network_fetches());
352 
353           // If there is already a session with |new_key|, skip this one.
354           // It will be found in |aliases_| in a future iteration.
355           if (available_sessions_.find(new_key) != available_sessions_.end()) {
356             continue;
357           }
358 
359           if (!available_session->ChangeSocketTag(key.socket_tag())) {
360             continue;
361           }
362 
363           DCHECK(available_session->spdy_session_key() == new_key);
364 
365           // If this isn't a pooled alias, but the actual session that needs to
366           // have its socket tag change, there's no need to add an alias.
367           if (new_key == key) {
368             adding_pooled_alias = false;
369           }
370 
371           // Remap main session key.
372           std::set<std::string> main_session_old_dns_aliases =
373               GetDnsAliasesForSessionKey(old_key);
374           UnmapKey(old_key);
375           MapKeyToAvailableSession(new_key, available_session,
376                                    std::move(main_session_old_dns_aliases));
377 
378           // Remap alias. From this point on |alias_it| is invalid, so no more
379           // iterations of the loop should be allowed.
380           aliases_.insert(AliasMap::value_type(alias_it->first, new_key));
381           aliases_.erase(alias_it);
382 
383           // Remap pooled session keys.
384           const auto& pooled_aliases = available_session->pooled_aliases();
385           for (auto it = pooled_aliases.begin(); it != pooled_aliases.end();) {
386             // Ignore aliases this loop is inserting.
387             if (it->socket_tag() == key.socket_tag()) {
388               ++it;
389               continue;
390             }
391 
392             std::set<std::string> pooled_alias_old_dns_aliases =
393                 GetDnsAliasesForSessionKey(*it);
394             UnmapKey(*it);
395             SpdySessionKey new_pool_alias_key = SpdySessionKey(
396                 it->host_port_pair(), it->privacy_mode(), it->proxy_chain(),
397                 it->session_usage(), key.socket_tag(),
398                 it->network_anonymization_key(), it->secure_dns_policy(),
399                 it->disable_cert_verification_network_fetches());
400             MapKeyToAvailableSession(new_pool_alias_key, available_session,
401                                      std::move(pooled_alias_old_dns_aliases));
402             auto old_it = it;
403             ++it;
404             available_session->RemovePooledAlias(*old_it);
405             available_session->AddPooledAlias(new_pool_alias_key);
406 
407             // If this is desired key, no need to add an alias for the desired
408             // key at the end of this method.
409             if (new_pool_alias_key == key) {
410               adding_pooled_alias = false;
411             }
412           }
413         }
414 
415         if (adding_pooled_alias) {
416           // Add this session to the map so that we can find it next time.
417           MapKeyToAvailableSession(key, available_session, aliases);
418           available_session->AddPooledAlias(key);
419         }
420 
421         // Post task to inform pending requests for session for |key| that a
422         // matching session is now available.
423         base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
424             FROM_HERE, base::BindOnce(&SpdySessionPool::UpdatePendingRequests,
425                                       weak_ptr_factory_.GetWeakPtr(), key));
426 
427         // Inform the caller that the Callback may be deleted if the consumer is
428         // switched over to the newly aliased session. It's not guaranteed to be
429         // deleted, as the session may be closed, or taken by yet another
430         // pending request with a different SocketTag before the the request can
431         // try and use the session.
432         return OnHostResolutionCallbackResult::kMayBeDeletedAsync;
433       }
434     }
435   }
436   return OnHostResolutionCallbackResult::kContinue;
437 }
438 
MakeSessionUnavailable(const base::WeakPtr<SpdySession> & available_session)439 void SpdySessionPool::MakeSessionUnavailable(
440     const base::WeakPtr<SpdySession>& available_session) {
441   UnmapKey(available_session->spdy_session_key());
442   RemoveAliases(available_session->spdy_session_key());
443   const std::set<SpdySessionKey>& aliases = available_session->pooled_aliases();
444   for (const auto& alias : aliases) {
445     UnmapKey(alias);
446     RemoveAliases(alias);
447   }
448   DCHECK(!IsSessionAvailable(available_session));
449 }
450 
RemoveUnavailableSession(const base::WeakPtr<SpdySession> & unavailable_session)451 void SpdySessionPool::RemoveUnavailableSession(
452     const base::WeakPtr<SpdySession>& unavailable_session) {
453   DCHECK(!IsSessionAvailable(unavailable_session));
454 
455   unavailable_session->net_log().AddEvent(
456       NetLogEventType::HTTP2_SESSION_POOL_REMOVE_SESSION);
457 
458   auto it = sessions_.find(unavailable_session.get());
459   CHECK(it != sessions_.end());
460   std::unique_ptr<SpdySession> owned_session(*it);
461   sessions_.erase(it);
462 }
463 
464 // Make a copy of |sessions_| in the Close* functions below to avoid
465 // reentrancy problems. Since arbitrary functions get called by close
466 // handlers, it doesn't suffice to simply increment the iterator
467 // before closing.
468 
CloseCurrentSessions(Error error)469 void SpdySessionPool::CloseCurrentSessions(Error error) {
470   CloseCurrentSessionsHelper(error, "Closing current sessions.",
471                              false /* idle_only */);
472 }
473 
CloseCurrentIdleSessions(const std::string & description)474 void SpdySessionPool::CloseCurrentIdleSessions(const std::string& description) {
475   CloseCurrentSessionsHelper(ERR_ABORTED, description, true /* idle_only */);
476 }
477 
CloseAllSessions()478 void SpdySessionPool::CloseAllSessions() {
479   auto is_draining = [](const SpdySession* s) { return s->IsDraining(); };
480   // Repeat until every SpdySession owned by |this| is draining.
481   while (!base::ranges::all_of(sessions_, is_draining)) {
482     CloseCurrentSessionsHelper(ERR_ABORTED, "Closing all sessions.",
483                                false /* idle_only */);
484   }
485 }
486 
MakeCurrentSessionsGoingAway(Error error)487 void SpdySessionPool::MakeCurrentSessionsGoingAway(Error error) {
488   WeakSessionList current_sessions = GetCurrentSessions();
489   for (base::WeakPtr<SpdySession>& session : current_sessions) {
490     if (!session) {
491       continue;
492     }
493 
494     session->MakeUnavailable();
495     session->StartGoingAway(kLastStreamId, error);
496     session->MaybeFinishGoingAway();
497     DCHECK(!IsSessionAvailable(session));
498   }
499 }
500 
SpdySessionPoolInfoToValue() const501 std::unique_ptr<base::Value> SpdySessionPool::SpdySessionPoolInfoToValue()
502     const {
503   base::Value::List list;
504 
505   for (const auto& available_session : available_sessions_) {
506     // Only add the session if the key in the map matches the main
507     // host_port_proxy_pair (not an alias).
508     const SpdySessionKey& key = available_session.first;
509     const SpdySessionKey& session_key =
510         available_session.second->spdy_session_key();
511     if (key == session_key)
512       list.Append(available_session.second->GetInfoAsValue());
513   }
514   return std::make_unique<base::Value>(std::move(list));
515 }
516 
OnIPAddressChanged()517 void SpdySessionPool::OnIPAddressChanged() {
518   DCHECK(cleanup_sessions_on_ip_address_changed_);
519   if (go_away_on_ip_change_) {
520     MakeCurrentSessionsGoingAway(ERR_NETWORK_CHANGED);
521   } else {
522     CloseCurrentSessions(ERR_NETWORK_CHANGED);
523   }
524 }
525 
OnSSLConfigChanged(SSLClientContext::SSLConfigChangeType change_type)526 void SpdySessionPool::OnSSLConfigChanged(
527     SSLClientContext::SSLConfigChangeType change_type) {
528   switch (change_type) {
529     case SSLClientContext::SSLConfigChangeType::kSSLConfigChanged:
530       MakeCurrentSessionsGoingAway(ERR_NETWORK_CHANGED);
531       break;
532     case SSLClientContext::SSLConfigChangeType::kCertDatabaseChanged:
533       MakeCurrentSessionsGoingAway(ERR_CERT_DATABASE_CHANGED);
534       break;
535     case SSLClientContext::SSLConfigChangeType::kCertVerifierChanged:
536       MakeCurrentSessionsGoingAway(ERR_CERT_VERIFIER_CHANGED);
537       break;
538   };
539 }
540 
OnSSLConfigForServersChanged(const base::flat_set<HostPortPair> & servers)541 void SpdySessionPool::OnSSLConfigForServersChanged(
542     const base::flat_set<HostPortPair>& servers) {
543   WeakSessionList current_sessions = GetCurrentSessions();
544   for (base::WeakPtr<SpdySession>& session : current_sessions) {
545     bool session_matches = false;
546     if (!session)
547       continue;
548 
549     // If the destination for this session is invalidated, or any of the proxy
550     // hops along the way, make the session go away.
551     if (servers.contains(session->host_port_pair())) {
552       session_matches = true;
553     } else {
554       const ProxyChain& proxy_chain = session->spdy_session_key().proxy_chain();
555 
556       for (const ProxyServer& proxy_server : proxy_chain.proxy_servers()) {
557         if (proxy_server.is_http_like() && !proxy_server.is_http() &&
558             servers.contains(proxy_server.host_port_pair())) {
559           session_matches = true;
560           break;
561         }
562       }
563     }
564 
565     if (session_matches) {
566       session->MakeUnavailable();
567       // Note this call preserves active streams but fails any streams that are
568       // waiting on a stream ID.
569       // TODO(https://crbug.com/1213609): This is not ideal, but SpdySession
570       // does not have a state that supports this.
571       session->StartGoingAway(kLastStreamId, ERR_NETWORK_CHANGED);
572       session->MaybeFinishGoingAway();
573       DCHECK(!IsSessionAvailable(session));
574     }
575   }
576 }
577 
GetDnsAliasesForSessionKey(const SpdySessionKey & key) const578 std::set<std::string> SpdySessionPool::GetDnsAliasesForSessionKey(
579     const SpdySessionKey& key) const {
580   auto it = dns_aliases_by_session_key_.find(key);
581   if (it == dns_aliases_by_session_key_.end())
582     return {};
583 
584   return it->second;
585 }
586 
RemoveRequestForSpdySession(SpdySessionRequest * request)587 void SpdySessionPool::RemoveRequestForSpdySession(SpdySessionRequest* request) {
588   DCHECK_EQ(this, request->spdy_session_pool());
589 
590   auto iter = spdy_session_request_map_.find(request->key());
591   DCHECK(iter != spdy_session_request_map_.end());
592 
593   // Resume all pending requests if it is the blocking request, which is either
594   // being canceled, or has completed.
595   if (request->is_blocking_request_for_session() &&
596       !iter->second.deferred_callbacks.empty()) {
597     base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
598         FROM_HERE,
599         base::BindOnce(&SpdySessionPool::UpdatePendingRequests,
600                        weak_ptr_factory_.GetWeakPtr(), request->key()));
601   }
602 
603   DCHECK(base::Contains(iter->second.request_set, request));
604   RemoveRequestInternal(iter, iter->second.request_set.find(request));
605 }
606 
607 SpdySessionPool::RequestInfoForKey::RequestInfoForKey() = default;
608 SpdySessionPool::RequestInfoForKey::~RequestInfoForKey() = default;
609 
IsSessionAvailable(const base::WeakPtr<SpdySession> & session) const610 bool SpdySessionPool::IsSessionAvailable(
611     const base::WeakPtr<SpdySession>& session) const {
612   for (const auto& available_session : available_sessions_) {
613     if (available_session.second.get() == session.get())
614       return true;
615   }
616   return false;
617 }
618 
MapKeyToAvailableSession(const SpdySessionKey & key,const base::WeakPtr<SpdySession> & session,std::set<std::string> dns_aliases)619 void SpdySessionPool::MapKeyToAvailableSession(
620     const SpdySessionKey& key,
621     const base::WeakPtr<SpdySession>& session,
622     std::set<std::string> dns_aliases) {
623   DCHECK(base::Contains(sessions_, session.get()));
624   std::pair<AvailableSessionMap::iterator, bool> result =
625       available_sessions_.emplace(key, session);
626   CHECK(result.second);
627 
628   dns_aliases_by_session_key_[key] = std::move(dns_aliases);
629 }
630 
631 SpdySessionPool::AvailableSessionMap::iterator
LookupAvailableSessionByKey(const SpdySessionKey & key)632 SpdySessionPool::LookupAvailableSessionByKey(
633     const SpdySessionKey& key) {
634   return available_sessions_.find(key);
635 }
636 
UnmapKey(const SpdySessionKey & key)637 void SpdySessionPool::UnmapKey(const SpdySessionKey& key) {
638   auto it = LookupAvailableSessionByKey(key);
639   CHECK(it != available_sessions_.end());
640   available_sessions_.erase(it);
641   dns_aliases_by_session_key_.erase(key);
642 }
643 
RemoveAliases(const SpdySessionKey & key)644 void SpdySessionPool::RemoveAliases(const SpdySessionKey& key) {
645   // Walk the aliases map, find references to this pair.
646   // TODO(mbelshe):  Figure out if this is too expensive.
647   for (auto it = aliases_.begin(); it != aliases_.end();) {
648     if (it->second == key) {
649       auto old_it = it;
650       ++it;
651       aliases_.erase(old_it);
652     } else {
653       ++it;
654     }
655   }
656 }
657 
GetCurrentSessions() const658 SpdySessionPool::WeakSessionList SpdySessionPool::GetCurrentSessions() const {
659   WeakSessionList current_sessions;
660   for (SpdySession* session : sessions_) {
661     current_sessions.push_back(session->GetWeakPtr());
662   }
663   return current_sessions;
664 }
665 
CloseCurrentSessionsHelper(Error error,const std::string & description,bool idle_only)666 void SpdySessionPool::CloseCurrentSessionsHelper(Error error,
667                                                  const std::string& description,
668                                                  bool idle_only) {
669   WeakSessionList current_sessions = GetCurrentSessions();
670   for (base::WeakPtr<SpdySession>& session : current_sessions) {
671     if (!session)
672       continue;
673 
674     if (idle_only && session->is_active())
675       continue;
676 
677     if (session->IsDraining())
678       continue;
679 
680     session->CloseSessionOnError(error, description);
681 
682     DCHECK(!IsSessionAvailable(session));
683     DCHECK(!session || session->IsDraining());
684   }
685 }
686 
CreateSession(const SpdySessionKey & key,NetLog * net_log)687 std::unique_ptr<SpdySession> SpdySessionPool::CreateSession(
688     const SpdySessionKey& key,
689     NetLog* net_log) {
690   UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", IMPORTED_FROM_SOCKET,
691                             SPDY_SESSION_GET_MAX);
692 
693   // If there's a pre-existing matching session, it has to be an alias. Remove
694   // the alias.
695   auto it = LookupAvailableSessionByKey(key);
696   if (it != available_sessions_.end()) {
697     DCHECK(key != it->second->spdy_session_key());
698 
699     // Remove session from available sessions and from aliases, and remove
700     // key from the session's pooled alias set, so that a new session can be
701     // created with this |key|.
702     it->second->RemovePooledAlias(key);
703     UnmapKey(key);
704     RemoveAliases(key);
705   }
706 
707   return std::make_unique<SpdySession>(
708       key, http_server_properties_, transport_security_state_,
709       ssl_client_context_ ? ssl_client_context_->ssl_config_service() : nullptr,
710       quic_supported_versions_, enable_sending_initial_data_,
711       enable_ping_based_connection_checking_, is_http2_enabled_,
712       is_quic_enabled_, session_max_recv_window_size_,
713       session_max_queued_capped_frames_, initial_settings_,
714       enable_http2_settings_grease_, greased_http2_frame_,
715       http2_end_stream_with_data_frame_, enable_priority_update_, time_func_,
716       network_quality_estimator_, net_log);
717 }
718 
InsertSession(const SpdySessionKey & key,std::unique_ptr<SpdySession> new_session,const NetLogWithSource & source_net_log,std::set<std::string> dns_aliases)719 base::WeakPtr<SpdySession> SpdySessionPool::InsertSession(
720     const SpdySessionKey& key,
721     std::unique_ptr<SpdySession> new_session,
722     const NetLogWithSource& source_net_log,
723     std::set<std::string> dns_aliases) {
724   base::WeakPtr<SpdySession> available_session = new_session->GetWeakPtr();
725   sessions_.insert(new_session.release());
726   MapKeyToAvailableSession(key, available_session, std::move(dns_aliases));
727 
728   base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
729       FROM_HERE, base::BindOnce(&SpdySessionPool::UpdatePendingRequests,
730                                 weak_ptr_factory_.GetWeakPtr(), key));
731 
732   source_net_log.AddEventReferencingSource(
733       NetLogEventType::HTTP2_SESSION_POOL_IMPORTED_SESSION_FROM_SOCKET,
734       available_session->net_log().source());
735 
736   // Look up the IP address for this session so that we can match
737   // future sessions (potentially to different domains) which can
738   // potentially be pooled with this one. Because GetPeerAddress()
739   // reports the proxy's address instead of the origin server, check
740   // to see if this is a direct connection.
741   if (key.proxy_chain().is_direct()) {
742     IPEndPoint address;
743     if (available_session->GetPeerAddress(&address) == OK)
744       aliases_.insert(AliasMap::value_type(address, key));
745   }
746 
747   return available_session;
748 }
749 
UpdatePendingRequests(const SpdySessionKey & key)750 void SpdySessionPool::UpdatePendingRequests(const SpdySessionKey& key) {
751   auto it = LookupAvailableSessionByKey(key);
752   if (it != available_sessions_.end()) {
753     base::WeakPtr<SpdySession> new_session = it->second->GetWeakPtr();
754     bool is_pooled = (key != new_session->spdy_session_key());
755     while (new_session && new_session->IsAvailable()) {
756       // Each iteration may empty out the RequestSet for |spdy_session_key| in
757       // |spdy_session_request_map_|. So each time, check for RequestSet and use
758       // the first one. Could just keep track if the last iteration removed the
759       // final request, but it's possible that responding to one request will
760       // result in cancelling another one.
761       //
762       // TODO(willchan): If it's important, switch RequestSet out for a FIFO
763       // queue (Order by priority first, then FIFO within same priority).
764       // Unclear that it matters here.
765       auto iter = spdy_session_request_map_.find(key);
766       if (iter == spdy_session_request_map_.end())
767         break;
768       RequestSet* request_set = &iter->second.request_set;
769       // Find a request that can use the socket, if any.
770       RequestSet::iterator request;
771       for (request = request_set->begin(); request != request_set->end();
772            ++request) {
773         // If the request is for use with websockets, and the session doesn't
774         // support websockets, skip over the request.
775         if ((*request)->is_websocket() && !new_session->support_websocket())
776           continue;
777         // Don't use IP pooled session if not allowed.
778         if (!(*request)->enable_ip_based_pooling() && is_pooled)
779           continue;
780         break;
781       }
782       if (request == request_set->end())
783         break;
784 
785       SpdySessionRequest::Delegate* delegate = (*request)->delegate();
786       RemoveRequestInternal(iter, request);
787       delegate->OnSpdySessionAvailable(new_session);
788     }
789   }
790 
791   auto iter = spdy_session_request_map_.find(key);
792   if (iter == spdy_session_request_map_.end())
793     return;
794   // Remove all pending requests, if there are any. As a result, if one of these
795   // callbacks triggers a new RequestSession() call,
796   // |is_blocking_request_for_session| will be true.
797   std::list<base::RepeatingClosure> deferred_requests =
798       std::move(iter->second.deferred_callbacks);
799 
800   // Delete the RequestMap if there are no SpdySessionRequests, and no deferred
801   // requests.
802   if (iter->second.request_set.empty())
803     spdy_session_request_map_.erase(iter);
804 
805   // Resume any deferred requests. This needs to be after the
806   // OnSpdySessionAvailable() calls, to prevent requests from calling into the
807   // socket pools in cases where that's not necessary.
808   for (auto callback : deferred_requests) {
809     callback.Run();
810   }
811 }
812 
RemoveRequestInternal(SpdySessionRequestMap::iterator request_map_iterator,RequestSet::iterator request_set_iterator)813 void SpdySessionPool::RemoveRequestInternal(
814     SpdySessionRequestMap::iterator request_map_iterator,
815     RequestSet::iterator request_set_iterator) {
816   SpdySessionRequest* request = *request_set_iterator;
817   request_map_iterator->second.request_set.erase(request_set_iterator);
818   if (request->is_blocking_request_for_session()) {
819     DCHECK(request_map_iterator->second.has_blocking_request);
820     request_map_iterator->second.has_blocking_request = false;
821   }
822 
823   // If both lists of requests are empty, can now remove the entry from the map.
824   if (request_map_iterator->second.request_set.empty() &&
825       request_map_iterator->second.deferred_callbacks.empty()) {
826     spdy_session_request_map_.erase(request_map_iterator);
827   }
828   request->OnRemovedFromPool();
829 }
830 
831 }  // namespace net
832