spdy_session_pool.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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. 4 5#include "net/spdy/spdy_session_pool.h" 6 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" 15 16 17namespace net { 18 19namespace { 20 21enum SpdySessionGetTypes { 22 CREATED_NEW = 0, 23 FOUND_EXISTING = 1, 24 FOUND_EXISTING_FROM_IP_POOL = 2, 25 IMPORTED_FROM_SOCKET = 3, 26 SPDY_SESSION_GET_MAX = 4 27}; 28 29bool HostPortProxyPairsAreEqual(const HostPortProxyPair& a, 30 const HostPortProxyPair& b) { 31 return a.first.Equals(b.first) && a.second == b.second; 32} 33 34} 35 36// The maximum number of sessions to open to a single domain. 37static const size_t kMaxSessionsPerDomain = 1; 38 39SpdySessionPool::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 stream_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), 57 resolver_(resolver), 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 stream_initial_recv_window_size_(stream_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), 74 trusted_spdy_proxy_( 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); 80} 81 82SpdySessionPool::~SpdySessionPool() { 83 CloseAllSessions(); 84 85 if (ssl_config_service_) 86 ssl_config_service_->RemoveObserver(this); 87 NetworkChangeNotifier::RemoveIPAddressObserver(this); 88 CertDatabase::GetInstance()->RemoveObserver(this); 89} 90 91scoped_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); 95} 96 97scoped_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); 101} 102 103scoped_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); 109 if (!list) { 110 // Check if we have a Session through a domain alias. 111 spdy_session = GetFromAlias(host_port_proxy_pair, net_log, true); 112 if (spdy_session) { 113 UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", 114 FOUND_EXISTING_FROM_IP_POOL, 115 SPDY_SESSION_GET_MAX); 116 net_log.AddEvent( 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); 123 return spdy_session; 124 } else if (only_use_existing_sessions) { 125 return NULL; 126 } 127 list = AddSessionList(host_port_proxy_pair); 128 } 129 130 DCHECK(list); 131 if (list->size() && list->size() == max_sessions_per_domain_) { 132 UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", 133 FOUND_EXISTING, 134 SPDY_SESSION_GET_MAX); 135 spdy_session = GetExistingSession(list, net_log); 136 net_log.AddEvent( 137 NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION, 138 spdy_session->net_log().source().ToEventParametersCallback()); 139 return spdy_session; 140 } 141 142 DCHECK(!only_use_existing_sessions); 143 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_, 149 enable_compression_, 150 enable_ping_based_connection_checking_, 151 default_protocol_, 152 stream_initial_recv_window_size_, 153 initial_max_concurrent_streams_, 154 max_concurrent_streams_limit_, 155 time_func_, 156 trusted_spdy_proxy_, 157 net_log.net_log()); 158 UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", 159 CREATED_NEW, 160 SPDY_SESSION_GET_MAX); 161 list->push_back(spdy_session); 162 net_log.AddEvent( 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_); 166 return spdy_session; 167} 168 169net::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, 175 bool is_secure) { 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_, 185 enable_compression_, 186 enable_ping_based_connection_checking_, 187 default_protocol_, 188 stream_initial_recv_window_size_, 189 initial_max_concurrent_streams_, 190 max_concurrent_streams_limit_, 191 time_func_, 192 trusted_spdy_proxy_, 193 net_log.net_log()); 194 SpdySessionList* list = GetSessionList(host_port_proxy_pair); 195 if (!list) 196 list = AddSessionList(host_port_proxy_pair); 197 DCHECK(list->empty()); 198 list->push_back(*spdy_session); 199 200 net_log.AddEvent( 201 NetLog::TYPE_SPDY_SESSION_POOL_IMPORTED_SESSION_FROM_SOCKET, 202 (*spdy_session)->net_log().source().ToEventParametersCallback()); 203 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()) { 210 IPEndPoint address; 211 if (connection->socket()->GetPeerAddress(&address) == OK) 212 AddAlias(address, host_port_proxy_pair); 213 } 214 215 // Now we can initialize the session with the SSL socket. 216 return (*spdy_session)->InitializeWithSocket(connection, is_secure, 217 certificate_error_code); 218} 219 220bool SpdySessionPool::HasSession( 221 const HostPortProxyPair& host_port_proxy_pair) const { 222 if (GetSessionList(host_port_proxy_pair)) 223 return true; 224 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; 229} 230 231void SpdySessionPool::Remove(const scoped_refptr<SpdySession>& session) { 232 bool ok = RemoveFromSessionList(session, session->host_port_proxy_pair()); 233 DCHECK(ok); 234 session->net_log().AddEvent( 235 NetLog::TYPE_SPDY_SESSION_POOL_REMOVE_SESSION, 236 session->net_log().source().ToEventParametersCallback()); 237 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); 242 DCHECK(ok); 243 } 244} 245 246bool SpdySessionPool::RemoveFromSessionList( 247 const scoped_refptr<SpdySession>& session, 248 const HostPortProxyPair& pair) { 249 SpdySessionList* list = GetSessionList(pair); 250 if (!list) 251 return false; 252 list->remove(session); 253 if (list->empty()) 254 RemoveSessionList(pair); 255 return true; 256} 257 258Value* SpdySessionPool::SpdySessionPoolInfoToValue() const { 259 ListValue* list = new ListValue(); 260 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()); 272 } 273 } 274 return list; 275} 276 277void SpdySessionPool::OnIPAddressChanged() { 278 CloseCurrentSessions(ERR_NETWORK_CHANGED); 279 http_server_properties_->ClearAllSpdySettings(); 280} 281 282void SpdySessionPool::OnSSLConfigChanged() { 283 CloseCurrentSessions(ERR_NETWORK_CHANGED); 284} 285 286scoped_refptr<SpdySession> SpdySessionPool::GetExistingSession( 287 SpdySessionList* list, 288 const BoundNetLog& net_log) const { 289 DCHECK(list); 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); 295 } 296 297 return spdy_session; 298} 299 300scoped_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)); 306 307 if (!enable_ip_pooling_) 308 return NULL; 309 310 AddressList addresses; 311 if (!LookupAddresses(host_port_proxy_pair, net_log, &addresses)) 312 return NULL; 313 for (AddressList::const_iterator iter = addresses.begin(); 314 iter != addresses.end(); 315 ++iter) { 316 SpdyAliasMap::const_iterator alias_iter = aliases_.find(*iter); 317 if (alias_iter == aliases_.end()) 318 continue; 319 320 // We found an alias. 321 const HostPortProxyPair& alias_pair = alias_iter->second; 322 323 // If the proxy settings match, we can reuse this session. 324 if (!(alias_pair.second == host_port_proxy_pair.second)) 325 continue; 326 327 SpdySessionList* list = GetSessionList(alias_pair); 328 if (!list) { 329 NOTREACHED(); // It shouldn't be in the aliases table if we can't get it! 330 continue; 331 } 332 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); 340 continue; 341 } 342 if (record_histograms) 343 UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 1, 2); 344 return spdy_session; 345 } 346 return NULL; 347} 348 349void SpdySessionPool::OnCertAdded(const X509Certificate* cert) { 350 CloseCurrentSessions(ERR_NETWORK_CHANGED); 351} 352 353void 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 356 // tell us this. 357 // See comments in ClientSocketPoolManager::OnCertTrustChanged. 358 CloseCurrentSessions(ERR_NETWORK_CHANGED); 359} 360 361const HostPortProxyPair& SpdySessionPool::NormalizeListPair( 362 const HostPortProxyPair& host_port_proxy_pair) const { 363 if (!force_single_domain_) 364 return host_port_proxy_pair; 365 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()); 371 } 372 return *single_domain_pair; 373} 374 375SpdySessionPool::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; 382 return list; 383} 384 385SpdySessionPool::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()) 391 return it->second; 392 return NULL; 393} 394 395void SpdySessionPool::RemoveSessionList( 396 const HostPortProxyPair& host_port_proxy_pair) { 397 const HostPortProxyPair& pair = NormalizeListPair(host_port_proxy_pair); 398 SpdySessionList* list = GetSessionList(pair); 399 if (list) { 400 delete list; 401 sessions_.erase(pair); 402 } else { 403 DCHECK(false) << "removing orphaned session list"; 404 } 405 RemoveAliases(host_port_proxy_pair); 406} 407 408bool 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); 414 return rv == OK; 415} 416 417void SpdySessionPool::AddAlias(const IPEndPoint& endpoint, 418 const HostPortProxyPair& pair) { 419 DCHECK(enable_ip_pooling_); 420 aliases_[endpoint] = pair; 421} 422 423void 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. 431 continue; 432 } 433 ++alias_it; 434 } 435} 436 437void SpdySessionPool::CloseAllSessions() { 438 while (!sessions_.empty()) { 439 SpdySessionList* list = sessions_.begin()->second; 440 CHECK(list); 441 const scoped_refptr<SpdySession>& session = list->front(); 442 CHECK(session); 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."); 447 } 448} 449 450void 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; 456 CHECK(list); 457 const scoped_refptr<SpdySession>& session = list->front(); 458 CHECK(session); 459 session->set_spdy_session_pool(NULL); 460 } 461 462 while (!old_map.empty()) { 463 SpdySessionList* list = old_map.begin()->second; 464 CHECK(list); 465 const scoped_refptr<SpdySession>& session = list->front(); 466 CHECK(session); 467 session->CloseSessionOnError(error, false, "Closing current sessions."); 468 list->pop_front(); 469 if (list->empty()) { 470 delete list; 471 RemoveAliases(old_map.begin()->first); 472 old_map.erase(old_map.begin()->first); 473 } 474 } 475 DCHECK(sessions_.empty()); 476 DCHECK(aliases_.empty()); 477} 478 479void SpdySessionPool::CloseIdleSessions() { 480 SpdySessionsMap::const_iterator map_it = sessions_.begin(); 481 while (map_it != sessions_.end()) { 482 SpdySessionList* list = map_it->second; 483 CHECK(list); 484 485 // Assumes there is only 1 element in the list. 486 SpdySessionList::iterator session_it = list->begin(); 487 const scoped_refptr<SpdySession>& session = *session_it; 488 CHECK(session); 489 if (session->is_active()) { 490 ++map_it; 491 continue; 492 } 493 494 HostPortProxyPair key = map_it->first; 495 session->CloseSessionOnError( 496 net::ERR_ABORTED, true, "Closing idle sessions."); 497 // CloseSessionOnError can invalidate the iterator. 498 map_it = sessions_.lower_bound(key); 499 } 500} 501 502} // namespace net 503