1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/spdy/spdy_session_pool.h"
7 #include "base/callback.h"
8 #include "base/logging.h"
9 #include "base/metrics/histogram.h"
10 #include "base/values.h"
11 #include "net/base/address_list.h"
12 #include "net/http/http_network_session.h"
13 #include "net/http/http_server_properties.h"
14 #include "net/spdy/spdy_session.h"
21 enum SpdySessionGetTypes
{
24 FOUND_EXISTING_FROM_IP_POOL
= 2,
25 IMPORTED_FROM_SOCKET
= 3,
26 SPDY_SESSION_GET_MAX
= 4
29 bool HostPortProxyPairsAreEqual(const HostPortProxyPair
& a
,
30 const HostPortProxyPair
& b
) {
31 return a
.first
.Equals(b
.first
) && a
.second
== b
.second
;
36 // The maximum number of sessions to open to a single domain.
37 static const size_t kMaxSessionsPerDomain
= 1;
39 SpdySessionPool::SpdySessionPool(
40 HostResolver
* resolver
,
41 SSLConfigService
* ssl_config_service
,
42 HttpServerProperties
* http_server_properties
,
43 size_t max_sessions_per_domain
,
44 bool force_single_domain
,
45 bool enable_ip_pooling
,
46 bool enable_credential_frames
,
47 bool enable_compression
,
48 bool enable_ping_based_connection_checking
,
49 NextProto default_protocol
,
50 size_t initial_recv_window_size
,
51 size_t initial_max_concurrent_streams
,
52 size_t max_concurrent_streams_limit
,
53 SpdySessionPool::TimeFunc time_func
,
54 const std::string
& trusted_spdy_proxy
)
55 : http_server_properties_(http_server_properties
),
56 ssl_config_service_(ssl_config_service
),
58 verify_domain_authentication_(true),
59 enable_sending_initial_settings_(true),
60 max_sessions_per_domain_(max_sessions_per_domain
== 0 ?
61 kMaxSessionsPerDomain
:
62 max_sessions_per_domain
),
63 force_single_domain_(force_single_domain
),
64 enable_ip_pooling_(enable_ip_pooling
),
65 enable_credential_frames_(enable_credential_frames
),
66 enable_compression_(enable_compression
),
67 enable_ping_based_connection_checking_(
68 enable_ping_based_connection_checking
),
69 default_protocol_(default_protocol
),
70 initial_recv_window_size_(initial_recv_window_size
),
71 initial_max_concurrent_streams_(initial_max_concurrent_streams
),
72 max_concurrent_streams_limit_(max_concurrent_streams_limit
),
73 time_func_(time_func
),
75 HostPortPair::FromString(trusted_spdy_proxy
)) {
76 NetworkChangeNotifier::AddIPAddressObserver(this);
77 if (ssl_config_service_
)
78 ssl_config_service_
->AddObserver(this);
79 CertDatabase::GetInstance()->AddObserver(this);
82 SpdySessionPool::~SpdySessionPool() {
85 if (ssl_config_service_
)
86 ssl_config_service_
->RemoveObserver(this);
87 NetworkChangeNotifier::RemoveIPAddressObserver(this);
88 CertDatabase::GetInstance()->RemoveObserver(this);
91 scoped_refptr
<SpdySession
> SpdySessionPool::Get(
92 const HostPortProxyPair
& host_port_proxy_pair
,
93 const BoundNetLog
& net_log
) {
94 return GetInternal(host_port_proxy_pair
, net_log
, false);
97 scoped_refptr
<SpdySession
> SpdySessionPool::GetIfExists(
98 const HostPortProxyPair
& host_port_proxy_pair
,
99 const BoundNetLog
& net_log
) {
100 return GetInternal(host_port_proxy_pair
, net_log
, true);
103 scoped_refptr
<SpdySession
> SpdySessionPool::GetInternal(
104 const HostPortProxyPair
& host_port_proxy_pair
,
105 const BoundNetLog
& net_log
,
106 bool only_use_existing_sessions
) {
107 scoped_refptr
<SpdySession
> spdy_session
;
108 SpdySessionList
* list
= GetSessionList(host_port_proxy_pair
);
110 // Check if we have a Session through a domain alias.
111 spdy_session
= GetFromAlias(host_port_proxy_pair
, net_log
, true);
113 UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
114 FOUND_EXISTING_FROM_IP_POOL
,
115 SPDY_SESSION_GET_MAX
);
117 NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION_FROM_IP_POOL
,
118 spdy_session
->net_log().source().ToEventParametersCallback());
119 // Add this session to the map so that we can find it next time.
120 list
= AddSessionList(host_port_proxy_pair
);
121 list
->push_back(spdy_session
);
122 spdy_session
->AddPooledAlias(host_port_proxy_pair
);
124 } else if (only_use_existing_sessions
) {
127 list
= AddSessionList(host_port_proxy_pair
);
131 if (list
->size() && list
->size() == max_sessions_per_domain_
) {
132 UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
134 SPDY_SESSION_GET_MAX
);
135 spdy_session
= GetExistingSession(list
, net_log
);
137 NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION
,
138 spdy_session
->net_log().source().ToEventParametersCallback());
142 DCHECK(!only_use_existing_sessions
);
144 spdy_session
= new SpdySession(host_port_proxy_pair
, this,
145 http_server_properties_
,
146 verify_domain_authentication_
,
147 enable_sending_initial_settings_
,
148 enable_credential_frames_
,
150 enable_ping_based_connection_checking_
,
152 initial_recv_window_size_
,
153 initial_max_concurrent_streams_
,
154 max_concurrent_streams_limit_
,
158 UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
160 SPDY_SESSION_GET_MAX
);
161 list
->push_back(spdy_session
);
163 NetLog::TYPE_SPDY_SESSION_POOL_CREATED_NEW_SESSION
,
164 spdy_session
->net_log().source().ToEventParametersCallback());
165 DCHECK_LE(list
->size(), max_sessions_per_domain_
);
169 net::Error
SpdySessionPool::GetSpdySessionFromSocket(
170 const HostPortProxyPair
& host_port_proxy_pair
,
171 ClientSocketHandle
* connection
,
172 const BoundNetLog
& net_log
,
173 int certificate_error_code
,
174 scoped_refptr
<SpdySession
>* spdy_session
,
176 UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
177 IMPORTED_FROM_SOCKET
,
178 SPDY_SESSION_GET_MAX
);
179 // Create the SPDY session and add it to the pool.
180 *spdy_session
= new SpdySession(host_port_proxy_pair
, this,
181 http_server_properties_
,
182 verify_domain_authentication_
,
183 enable_sending_initial_settings_
,
184 enable_credential_frames_
,
186 enable_ping_based_connection_checking_
,
188 initial_recv_window_size_
,
189 initial_max_concurrent_streams_
,
190 max_concurrent_streams_limit_
,
194 SpdySessionList
* list
= GetSessionList(host_port_proxy_pair
);
196 list
= AddSessionList(host_port_proxy_pair
);
197 DCHECK(list
->empty());
198 list
->push_back(*spdy_session
);
201 NetLog::TYPE_SPDY_SESSION_POOL_IMPORTED_SESSION_FROM_SOCKET
,
202 (*spdy_session
)->net_log().source().ToEventParametersCallback());
204 // We have a new session. Lookup the IP address for this session so that we
205 // can match future Sessions (potentially to different domains) which can
206 // potentially be pooled with this one. Because GetPeerAddress() reports the
207 // proxy's address instead of the origin server, check to see if this is a
208 // direct connection.
209 if (enable_ip_pooling_
&& host_port_proxy_pair
.second
.is_direct()) {
211 if (connection
->socket()->GetPeerAddress(&address
) == OK
)
212 AddAlias(address
, host_port_proxy_pair
);
215 // Now we can initialize the session with the SSL socket.
216 return (*spdy_session
)->InitializeWithSocket(connection
, is_secure
,
217 certificate_error_code
);
220 bool SpdySessionPool::HasSession(
221 const HostPortProxyPair
& host_port_proxy_pair
) const {
222 if (GetSessionList(host_port_proxy_pair
))
225 // Check if we have a session via an alias.
226 scoped_refptr
<SpdySession
> spdy_session
=
227 GetFromAlias(host_port_proxy_pair
, BoundNetLog(), false);
228 return spdy_session
.get() != NULL
;
231 void SpdySessionPool::Remove(const scoped_refptr
<SpdySession
>& session
) {
232 bool ok
= RemoveFromSessionList(session
, session
->host_port_proxy_pair());
234 session
->net_log().AddEvent(
235 NetLog::TYPE_SPDY_SESSION_POOL_REMOVE_SESSION
,
236 session
->net_log().source().ToEventParametersCallback());
238 const std::set
<HostPortProxyPair
>& aliases
= session
->pooled_aliases();
239 for (std::set
<HostPortProxyPair
>::const_iterator it
= aliases
.begin();
240 it
!= aliases
.end(); ++it
) {
241 ok
= RemoveFromSessionList(session
, *it
);
246 bool SpdySessionPool::RemoveFromSessionList(
247 const scoped_refptr
<SpdySession
>& session
,
248 const HostPortProxyPair
& pair
) {
249 SpdySessionList
* list
= GetSessionList(pair
);
252 list
->remove(session
);
254 RemoveSessionList(pair
);
258 Value
* SpdySessionPool::SpdySessionPoolInfoToValue() const {
259 ListValue
* list
= new ListValue();
261 for (SpdySessionsMap::const_iterator it
= sessions_
.begin();
262 it
!= sessions_
.end(); ++it
) {
263 SpdySessionList
* sessions
= it
->second
;
264 for (SpdySessionList::const_iterator session
= sessions
->begin();
265 session
!= sessions
->end(); ++session
) {
266 // Only add the session if the key in the map matches the main
267 // host_port_proxy_pair (not an alias).
268 const HostPortProxyPair
& key
= it
->first
;
269 const HostPortProxyPair
& pair
= session
->get()->host_port_proxy_pair();
270 if (key
.first
.Equals(pair
.first
) && key
.second
== pair
.second
)
271 list
->Append(session
->get()->GetInfoAsValue());
277 void SpdySessionPool::OnIPAddressChanged() {
278 CloseCurrentSessions(ERR_NETWORK_CHANGED
);
279 http_server_properties_
->ClearSpdySettings();
282 void SpdySessionPool::OnSSLConfigChanged() {
283 CloseCurrentSessions(ERR_NETWORK_CHANGED
);
286 scoped_refptr
<SpdySession
> SpdySessionPool::GetExistingSession(
287 SpdySessionList
* list
,
288 const BoundNetLog
& net_log
) const {
290 DCHECK_LT(0u, list
->size());
291 scoped_refptr
<SpdySession
> spdy_session
= list
->front();
292 if (list
->size() > 1) {
293 list
->pop_front(); // Rotate the list.
294 list
->push_back(spdy_session
);
300 scoped_refptr
<SpdySession
> SpdySessionPool::GetFromAlias(
301 const HostPortProxyPair
& host_port_proxy_pair
,
302 const BoundNetLog
& net_log
,
303 bool record_histograms
) const {
304 // We should only be checking aliases when there is no direct session.
305 DCHECK(!GetSessionList(host_port_proxy_pair
));
307 if (!enable_ip_pooling_
)
310 AddressList addresses
;
311 if (!LookupAddresses(host_port_proxy_pair
, net_log
, &addresses
))
313 for (AddressList::const_iterator iter
= addresses
.begin();
314 iter
!= addresses
.end();
316 SpdyAliasMap::const_iterator alias_iter
= aliases_
.find(*iter
);
317 if (alias_iter
== aliases_
.end())
320 // We found an alias.
321 const HostPortProxyPair
& alias_pair
= alias_iter
->second
;
323 // If the proxy settings match, we can reuse this session.
324 if (!(alias_pair
.second
== host_port_proxy_pair
.second
))
327 SpdySessionList
* list
= GetSessionList(alias_pair
);
329 NOTREACHED(); // It shouldn't be in the aliases table if we can't get it!
333 scoped_refptr
<SpdySession
> spdy_session
= GetExistingSession(list
, net_log
);
334 // If the SPDY session is a secure one, we need to verify that the server
335 // is authenticated to serve traffic for |host_port_proxy_pair| too.
336 if (!spdy_session
->VerifyDomainAuthentication(
337 host_port_proxy_pair
.first
.host())) {
338 if (record_histograms
)
339 UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 0, 2);
342 if (record_histograms
)
343 UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 1, 2);
349 void SpdySessionPool::OnCertAdded(const X509Certificate
* cert
) {
350 CloseCurrentSessions(ERR_NETWORK_CHANGED
);
353 void SpdySessionPool::OnCertTrustChanged(const X509Certificate
* cert
) {
354 // Per wtc, we actually only need to CloseCurrentSessions when trust is
355 // reduced. CloseCurrentSessions now because OnCertTrustChanged does not
357 // See comments in ClientSocketPoolManager::OnCertTrustChanged.
358 CloseCurrentSessions(ERR_NETWORK_CHANGED
);
361 const HostPortProxyPair
& SpdySessionPool::NormalizeListPair(
362 const HostPortProxyPair
& host_port_proxy_pair
) const {
363 if (!force_single_domain_
)
364 return host_port_proxy_pair
;
366 static HostPortProxyPair
* single_domain_pair
= NULL
;
367 if (!single_domain_pair
) {
368 HostPortPair single_domain
= HostPortPair("singledomain.com", 80);
369 single_domain_pair
= new HostPortProxyPair(single_domain
,
370 ProxyServer::Direct());
372 return *single_domain_pair
;
375 SpdySessionPool::SpdySessionList
*
376 SpdySessionPool::AddSessionList(
377 const HostPortProxyPair
& host_port_proxy_pair
) {
378 const HostPortProxyPair
& pair
= NormalizeListPair(host_port_proxy_pair
);
379 DCHECK(sessions_
.find(pair
) == sessions_
.end());
380 SpdySessionPool::SpdySessionList
* list
= new SpdySessionList();
381 sessions_
[pair
] = list
;
385 SpdySessionPool::SpdySessionList
*
386 SpdySessionPool::GetSessionList(
387 const HostPortProxyPair
& host_port_proxy_pair
) const {
388 const HostPortProxyPair
& pair
= NormalizeListPair(host_port_proxy_pair
);
389 SpdySessionsMap::const_iterator it
= sessions_
.find(pair
);
390 if (it
!= sessions_
.end())
395 void SpdySessionPool::RemoveSessionList(
396 const HostPortProxyPair
& host_port_proxy_pair
) {
397 const HostPortProxyPair
& pair
= NormalizeListPair(host_port_proxy_pair
);
398 SpdySessionList
* list
= GetSessionList(pair
);
401 sessions_
.erase(pair
);
403 DCHECK(false) << "removing orphaned session list";
405 RemoveAliases(host_port_proxy_pair
);
408 bool SpdySessionPool::LookupAddresses(const HostPortProxyPair
& pair
,
409 const BoundNetLog
& net_log
,
410 AddressList
* addresses
) const {
411 net::HostResolver::RequestInfo
resolve_info(pair
.first
);
412 int rv
= resolver_
->ResolveFromCache(resolve_info
, addresses
, net_log
);
413 DCHECK_NE(ERR_IO_PENDING
, rv
);
417 void SpdySessionPool::AddAlias(const IPEndPoint
& endpoint
,
418 const HostPortProxyPair
& pair
) {
419 DCHECK(enable_ip_pooling_
);
420 aliases_
[endpoint
] = pair
;
423 void SpdySessionPool::RemoveAliases(const HostPortProxyPair
& pair
) {
424 // Walk the aliases map, find references to this pair.
425 // TODO(mbelshe): Figure out if this is too expensive.
426 SpdyAliasMap::iterator alias_it
= aliases_
.begin();
427 while (alias_it
!= aliases_
.end()) {
428 if (HostPortProxyPairsAreEqual(alias_it
->second
, pair
)) {
429 aliases_
.erase(alias_it
);
430 alias_it
= aliases_
.begin(); // Iterator was invalidated.
437 void SpdySessionPool::CloseAllSessions() {
438 while (!sessions_
.empty()) {
439 SpdySessionList
* list
= sessions_
.begin()->second
;
441 const scoped_refptr
<SpdySession
>& session
= list
->front();
443 // This call takes care of removing the session from the pool, as well as
444 // removing the session list if the list is empty.
445 session
->CloseSessionOnError(
446 net::ERR_ABORTED
, true, "Closing all sessions.");
450 void SpdySessionPool::CloseCurrentSessions(net::Error error
) {
451 SpdySessionsMap old_map
;
452 old_map
.swap(sessions_
);
453 for (SpdySessionsMap::const_iterator it
= old_map
.begin();
454 it
!= old_map
.end(); ++it
) {
455 SpdySessionList
* list
= it
->second
;
457 const scoped_refptr
<SpdySession
>& session
= list
->front();
459 session
->set_spdy_session_pool(NULL
);
462 while (!old_map
.empty()) {
463 SpdySessionList
* list
= old_map
.begin()->second
;
465 const scoped_refptr
<SpdySession
>& session
= list
->front();
467 session
->CloseSessionOnError(error
, false, "Closing current sessions.");
471 RemoveAliases(old_map
.begin()->first
);
472 old_map
.erase(old_map
.begin()->first
);
475 DCHECK(sessions_
.empty());
476 DCHECK(aliases_
.empty());
479 void SpdySessionPool::CloseIdleSessions() {
480 SpdySessionsMap::const_iterator map_it
= sessions_
.begin();
481 while (map_it
!= sessions_
.end()) {
482 SpdySessionList
* list
= map_it
->second
;
486 // Assumes there is only 1 element in the list
487 SpdySessionList::iterator session_it
= list
->begin();
488 const scoped_refptr
<SpdySession
>& session
= *session_it
;
490 if (!session
->is_active()) {
491 session
->CloseSessionOnError(
492 net::ERR_ABORTED
, true, "Closing idle sessions.");