spdy_session_pool.cc revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
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/logging.h" 8#include "base/metrics/histogram.h" 9#include "base/values.h" 10#include "net/base/address_list.h" 11#include "net/http/http_network_session.h" 12#include "net/http/http_server_properties.h" 13#include "net/spdy/spdy_session.h" 14 15 16namespace net { 17 18namespace { 19 20enum SpdySessionGetTypes { 21 CREATED_NEW = 0, 22 FOUND_EXISTING = 1, 23 FOUND_EXISTING_FROM_IP_POOL = 2, 24 IMPORTED_FROM_SOCKET = 3, 25 SPDY_SESSION_GET_MAX = 4 26}; 27 28} // namespace 29 30SpdySessionPool::SpdySessionPool( 31 HostResolver* resolver, 32 SSLConfigService* ssl_config_service, 33 HttpServerProperties* http_server_properties, 34 bool force_single_domain, 35 bool enable_ip_pooling, 36 bool enable_credential_frames, 37 bool enable_compression, 38 bool enable_ping_based_connection_checking, 39 NextProto default_protocol, 40 size_t stream_initial_recv_window_size, 41 size_t initial_max_concurrent_streams, 42 size_t max_concurrent_streams_limit, 43 SpdySessionPool::TimeFunc time_func, 44 const std::string& trusted_spdy_proxy) 45 : http_server_properties_(http_server_properties), 46 ssl_config_service_(ssl_config_service), 47 resolver_(resolver), 48 verify_domain_authentication_(true), 49 enable_sending_initial_settings_(true), 50 force_single_domain_(force_single_domain), 51 enable_ip_pooling_(enable_ip_pooling), 52 enable_credential_frames_(enable_credential_frames), 53 enable_compression_(enable_compression), 54 enable_ping_based_connection_checking_( 55 enable_ping_based_connection_checking), 56 default_protocol_(default_protocol), 57 stream_initial_recv_window_size_(stream_initial_recv_window_size), 58 initial_max_concurrent_streams_(initial_max_concurrent_streams), 59 max_concurrent_streams_limit_(max_concurrent_streams_limit), 60 time_func_(time_func), 61 trusted_spdy_proxy_( 62 HostPortPair::FromString(trusted_spdy_proxy)) { 63 NetworkChangeNotifier::AddIPAddressObserver(this); 64 if (ssl_config_service_.get()) 65 ssl_config_service_->AddObserver(this); 66 CertDatabase::GetInstance()->AddObserver(this); 67} 68 69SpdySessionPool::~SpdySessionPool() { 70 CloseAllSessions(); 71 72 if (ssl_config_service_.get()) 73 ssl_config_service_->RemoveObserver(this); 74 NetworkChangeNotifier::RemoveIPAddressObserver(this); 75 CertDatabase::GetInstance()->RemoveObserver(this); 76} 77 78net::Error SpdySessionPool::CreateAvailableSessionFromSocket( 79 const SpdySessionKey& key, 80 scoped_ptr<ClientSocketHandle> connection, 81 const BoundNetLog& net_log, 82 int certificate_error_code, 83 scoped_refptr<SpdySession>* available_session, 84 bool is_secure) { 85 UMA_HISTOGRAM_ENUMERATION( 86 "Net.SpdySessionGet", IMPORTED_FROM_SOCKET, SPDY_SESSION_GET_MAX); 87 88 scoped_refptr<SpdySession> new_session( 89 new SpdySession(key, 90 http_server_properties_, 91 verify_domain_authentication_, 92 enable_sending_initial_settings_, 93 enable_credential_frames_, 94 enable_compression_, 95 enable_ping_based_connection_checking_, 96 default_protocol_, 97 stream_initial_recv_window_size_, 98 initial_max_concurrent_streams_, 99 max_concurrent_streams_limit_, 100 time_func_, 101 trusted_spdy_proxy_, 102 net_log.net_log())); 103 104 Error error = new_session->InitializeWithSocket( 105 connection.Pass(), this, is_secure, certificate_error_code); 106 DCHECK_NE(error, ERR_IO_PENDING); 107 108 if (error != OK) { 109 new_session = NULL; 110 *available_session = NULL; 111 return error; 112 } 113 114 sessions_.insert(new_session); 115 available_session->swap(new_session); 116 MapKeyToAvailableSession(key, *available_session); 117 118 net_log.AddEvent( 119 NetLog::TYPE_SPDY_SESSION_POOL_IMPORTED_SESSION_FROM_SOCKET, 120 (*available_session)->net_log().source().ToEventParametersCallback()); 121 122 // Look up the IP address for this session so that we can match 123 // future sessions (potentially to different domains) which can 124 // potentially be pooled with this one. Because GetPeerAddress() 125 // reports the proxy's address instead of the origin server, check 126 // to see if this is a direct connection. 127 if (enable_ip_pooling_ && key.proxy_server().is_direct()) { 128 IPEndPoint address; 129 if ((*available_session)->GetPeerAddress(&address) == OK) 130 aliases_[address] = key; 131 } 132 133 return error; 134} 135 136scoped_refptr<SpdySession> SpdySessionPool::FindAvailableSession( 137 const SpdySessionKey& key, 138 const BoundNetLog& net_log) { 139 AvailableSessionMap::iterator it = LookupAvailableSessionByKey(key); 140 if (it != available_sessions_.end()) { 141 UMA_HISTOGRAM_ENUMERATION( 142 "Net.SpdySessionGet", FOUND_EXISTING, SPDY_SESSION_GET_MAX); 143 net_log.AddEvent( 144 NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION, 145 it->second->net_log().source().ToEventParametersCallback()); 146 return it->second; 147 } 148 149 if (!enable_ip_pooling_) 150 return scoped_refptr<SpdySession>(); 151 152 // Look up the key's from the resolver's cache. 153 net::HostResolver::RequestInfo resolve_info(key.host_port_pair()); 154 AddressList addresses; 155 int rv = resolver_->ResolveFromCache(resolve_info, &addresses, net_log); 156 DCHECK_NE(rv, ERR_IO_PENDING); 157 if (rv != OK) 158 return scoped_refptr<SpdySession>(); 159 160 // Check if we have a session through a domain alias. 161 for (AddressList::const_iterator address_it = addresses.begin(); 162 address_it != addresses.end(); 163 ++address_it) { 164 AliasMap::const_iterator alias_it = aliases_.find(*address_it); 165 if (alias_it == aliases_.end()) 166 continue; 167 168 // We found an alias. 169 const SpdySessionKey& alias_key = alias_it->second; 170 171 // We can reuse this session only if the proxy and privacy 172 // settings match. 173 if (!(alias_key.proxy_server() == key.proxy_server()) || 174 !(alias_key.privacy_mode() == key.privacy_mode())) 175 continue; 176 177 AvailableSessionMap::iterator available_session_it = 178 LookupAvailableSessionByKey(alias_key); 179 if (available_session_it == available_sessions_.end()) { 180 NOTREACHED(); // It shouldn't be in the aliases table if we can't get it! 181 continue; 182 } 183 184 const scoped_refptr<SpdySession>& available_session = 185 available_session_it->second; 186 DCHECK(ContainsKey(sessions_, available_session)); 187 // If the session is a secure one, we need to verify that the 188 // server is authenticated to serve traffic for |host_port_proxy_pair| too. 189 if (!available_session->VerifyDomainAuthentication( 190 key.host_port_pair().host())) { 191 UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 0, 2); 192 continue; 193 } 194 195 UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 1, 2); 196 UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", 197 FOUND_EXISTING_FROM_IP_POOL, 198 SPDY_SESSION_GET_MAX); 199 net_log.AddEvent( 200 NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION_FROM_IP_POOL, 201 available_session->net_log().source().ToEventParametersCallback()); 202 // Add this session to the map so that we can find it next time. 203 MapKeyToAvailableSession(key, available_session); 204 available_session->AddPooledAlias(key); 205 return available_session; 206 } 207 208 return scoped_refptr<SpdySession>(); 209} 210 211void SpdySessionPool::MakeSessionUnavailable( 212 const scoped_refptr<SpdySession>& available_session) { 213 UnmapKey(available_session->spdy_session_key()); 214 RemoveAliases(available_session->spdy_session_key()); 215 const std::set<SpdySessionKey>& aliases = available_session->pooled_aliases(); 216 for (std::set<SpdySessionKey>::const_iterator it = aliases.begin(); 217 it != aliases.end(); ++it) { 218 UnmapKey(*it); 219 RemoveAliases(*it); 220 } 221 DCHECK(!IsSessionAvailable(available_session)); 222} 223 224void SpdySessionPool::RemoveUnavailableSession( 225 const scoped_refptr<SpdySession>& unavailable_session) { 226 DCHECK(!IsSessionAvailable(unavailable_session)); 227 228 unavailable_session->net_log().AddEvent( 229 NetLog::TYPE_SPDY_SESSION_POOL_REMOVE_SESSION, 230 unavailable_session->net_log().source().ToEventParametersCallback()); 231 232 SessionSet::iterator it = sessions_.find(unavailable_session); 233 CHECK(it != sessions_.end()); 234 sessions_.erase(it); 235} 236 237// Make a copy of |sessions_| in the Close* functions below to avoid 238// reentrancy problems. Since arbitrary functions get called by close 239// handlers, it doesn't suffice to simply increment the iterator 240// before closing. 241 242void SpdySessionPool::CloseCurrentSessions(net::Error error) { 243 CloseCurrentSessionsHelper(error, "Closing current sessions.", 244 false /* idle_only */); 245} 246 247void SpdySessionPool::CloseCurrentIdleSessions() { 248 CloseCurrentSessionsHelper(ERR_ABORTED, "Closing idle sessions.", 249 true /* idle_only */); 250} 251 252void SpdySessionPool::CloseAllSessions() { 253 while (!sessions_.empty()) { 254 CloseCurrentSessionsHelper(ERR_ABORTED, "Closing all sessions.", 255 false /* idle_only */); 256 } 257} 258 259base::Value* SpdySessionPool::SpdySessionPoolInfoToValue() const { 260 base::ListValue* list = new base::ListValue(); 261 262 for (AvailableSessionMap::const_iterator it = available_sessions_.begin(); 263 it != available_sessions_.end(); ++it) { 264 // Only add the session if the key in the map matches the main 265 // host_port_proxy_pair (not an alias). 266 const SpdySessionKey& key = it->first; 267 const SpdySessionKey& session_key = it->second->spdy_session_key(); 268 if (key.Equals(session_key)) 269 list->Append(it->second->GetInfoAsValue()); 270 } 271 return list; 272} 273 274void SpdySessionPool::OnIPAddressChanged() { 275 CloseCurrentSessions(ERR_NETWORK_CHANGED); 276 http_server_properties_->ClearAllSpdySettings(); 277} 278 279void SpdySessionPool::OnSSLConfigChanged() { 280 CloseCurrentSessions(ERR_NETWORK_CHANGED); 281} 282 283void SpdySessionPool::OnCertAdded(const X509Certificate* cert) { 284 CloseCurrentSessions(ERR_NETWORK_CHANGED); 285} 286 287void SpdySessionPool::OnCertTrustChanged(const X509Certificate* cert) { 288 // Per wtc, we actually only need to CloseCurrentSessions when trust is 289 // reduced. CloseCurrentSessions now because OnCertTrustChanged does not 290 // tell us this. 291 // See comments in ClientSocketPoolManager::OnCertTrustChanged. 292 CloseCurrentSessions(ERR_NETWORK_CHANGED); 293} 294 295bool SpdySessionPool::IsSessionAvailable( 296 const scoped_refptr<SpdySession>& session) const { 297 for (AvailableSessionMap::const_iterator it = available_sessions_.begin(); 298 it != available_sessions_.end(); ++it) { 299 if (it->second == session) 300 return true; 301 } 302 return false; 303} 304 305const SpdySessionKey& SpdySessionPool::NormalizeListKey( 306 const SpdySessionKey& key) const { 307 if (!force_single_domain_) 308 return key; 309 310 static SpdySessionKey* single_domain_key = NULL; 311 if (!single_domain_key) { 312 HostPortPair single_domain = HostPortPair("singledomain.com", 80); 313 single_domain_key = new SpdySessionKey(single_domain, 314 ProxyServer::Direct(), 315 kPrivacyModeDisabled); 316 } 317 return *single_domain_key; 318} 319 320void SpdySessionPool::MapKeyToAvailableSession( 321 const SpdySessionKey& key, 322 const scoped_refptr<SpdySession>& session) { 323 DCHECK(ContainsKey(sessions_, session)); 324 const SpdySessionKey& normalized_key = NormalizeListKey(key); 325 std::pair<AvailableSessionMap::iterator, bool> result = 326 available_sessions_.insert(std::make_pair(normalized_key, session)); 327 CHECK(result.second); 328} 329 330SpdySessionPool::AvailableSessionMap::iterator 331SpdySessionPool::LookupAvailableSessionByKey( 332 const SpdySessionKey& key) { 333 const SpdySessionKey& normalized_key = NormalizeListKey(key); 334 return available_sessions_.find(normalized_key); 335} 336 337void SpdySessionPool::UnmapKey(const SpdySessionKey& key) { 338 AvailableSessionMap::iterator it = LookupAvailableSessionByKey(key); 339 CHECK(it != available_sessions_.end()); 340 available_sessions_.erase(it); 341} 342 343void SpdySessionPool::RemoveAliases(const SpdySessionKey& key) { 344 // Walk the aliases map, find references to this pair. 345 // TODO(mbelshe): Figure out if this is too expensive. 346 for (AliasMap::iterator it = aliases_.begin(); it != aliases_.end(); ) { 347 if (it->second.Equals(key)) { 348 AliasMap::iterator old_it = it; 349 ++it; 350 aliases_.erase(old_it); 351 } else { 352 ++it; 353 } 354 } 355} 356 357void SpdySessionPool::CloseCurrentSessionsHelper( 358 Error error, 359 const std::string& description, 360 bool idle_only) { 361 SessionSet current_sessions = sessions_; 362 for (SessionSet::const_iterator it = current_sessions.begin(); 363 it != current_sessions.end(); ++it) { 364 if (!ContainsKey(sessions_, *it)) 365 continue; 366 367 // TODO(akalin): Handle unavailable sessions once those aren't 368 // removed immediately. 369 DCHECK(IsSessionAvailable(*it)); 370 371 if (idle_only && (*it)->is_active()) 372 continue; 373 374 (*it)->CloseSessionOnError(error, description); 375 DCHECK(!IsSessionAvailable(*it)); 376 DCHECK(!ContainsKey(sessions_, *it)); 377 } 378} 379 380} // namespace net 381