| OLD | NEW |
| (Empty) |
| 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/profiler/scoped_tracker.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 | |
| 17 namespace net { | |
| 18 | |
| 19 namespace { | |
| 20 | |
| 21 enum 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 | |
| 29 } // namespace | |
| 30 | |
| 31 SpdySessionPool::SpdySessionPool( | |
| 32 HostResolver* resolver, | |
| 33 SSLConfigService* ssl_config_service, | |
| 34 const base::WeakPtr<HttpServerProperties>& http_server_properties, | |
| 35 TransportSecurityState* transport_security_state, | |
| 36 bool force_single_domain, | |
| 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 transport_security_state_(transport_security_state), | |
| 47 ssl_config_service_(ssl_config_service), | |
| 48 resolver_(resolver), | |
| 49 verify_domain_authentication_(true), | |
| 50 enable_sending_initial_data_(true), | |
| 51 force_single_domain_(force_single_domain), | |
| 52 enable_compression_(enable_compression), | |
| 53 enable_ping_based_connection_checking_( | |
| 54 enable_ping_based_connection_checking), | |
| 55 // TODO(akalin): Force callers to have a valid value of | |
| 56 // |default_protocol_|. | |
| 57 default_protocol_( | |
| 58 (default_protocol == kProtoUnknown) ? | |
| 59 kProtoSPDY31 : default_protocol), | |
| 60 stream_initial_recv_window_size_(stream_initial_recv_window_size), | |
| 61 initial_max_concurrent_streams_(initial_max_concurrent_streams), | |
| 62 max_concurrent_streams_limit_(max_concurrent_streams_limit), | |
| 63 time_func_(time_func), | |
| 64 trusted_spdy_proxy_( | |
| 65 HostPortPair::FromString(trusted_spdy_proxy)) { | |
| 66 DCHECK(default_protocol_ >= kProtoSPDYMinimumVersion && | |
| 67 default_protocol_ <= kProtoSPDYMaximumVersion); | |
| 68 // TODO(michaeln): Remove ScopedTracker below once crbug.com/454983 is fixed | |
| 69 tracked_objects::ScopedTracker tracking_profile( | |
| 70 FROM_HERE_WITH_EXPLICIT_FUNCTION( | |
| 71 "454983 SpdySessionPool::SpdySessionPool")); | |
| 72 NetworkChangeNotifier::AddIPAddressObserver(this); | |
| 73 if (ssl_config_service_.get()) | |
| 74 ssl_config_service_->AddObserver(this); | |
| 75 CertDatabase::GetInstance()->AddObserver(this); | |
| 76 } | |
| 77 | |
| 78 SpdySessionPool::~SpdySessionPool() { | |
| 79 CloseAllSessions(); | |
| 80 | |
| 81 while (!sessions_.empty()) { | |
| 82 // Destroy sessions to enforce that lifetime is scoped to SpdySessionPool. | |
| 83 // Write callbacks queued upon session drain are not invoked. | |
| 84 RemoveUnavailableSession((*sessions_.begin())->GetWeakPtr()); | |
| 85 } | |
| 86 | |
| 87 if (ssl_config_service_.get()) | |
| 88 ssl_config_service_->RemoveObserver(this); | |
| 89 NetworkChangeNotifier::RemoveIPAddressObserver(this); | |
| 90 CertDatabase::GetInstance()->RemoveObserver(this); | |
| 91 } | |
| 92 | |
| 93 base::WeakPtr<SpdySession> SpdySessionPool::CreateAvailableSessionFromSocket( | |
| 94 const SpdySessionKey& key, | |
| 95 scoped_ptr<ClientSocketHandle> connection, | |
| 96 const BoundNetLog& net_log, | |
| 97 int certificate_error_code, | |
| 98 bool is_secure) { | |
| 99 DCHECK_GE(default_protocol_, kProtoSPDYMinimumVersion); | |
| 100 DCHECK_LE(default_protocol_, kProtoSPDYMaximumVersion); | |
| 101 | |
| 102 UMA_HISTOGRAM_ENUMERATION( | |
| 103 "Net.SpdySessionGet", IMPORTED_FROM_SOCKET, SPDY_SESSION_GET_MAX); | |
| 104 | |
| 105 scoped_ptr<SpdySession> new_session( | |
| 106 new SpdySession(key, | |
| 107 http_server_properties_, | |
| 108 transport_security_state_, | |
| 109 verify_domain_authentication_, | |
| 110 enable_sending_initial_data_, | |
| 111 enable_compression_, | |
| 112 enable_ping_based_connection_checking_, | |
| 113 default_protocol_, | |
| 114 stream_initial_recv_window_size_, | |
| 115 initial_max_concurrent_streams_, | |
| 116 max_concurrent_streams_limit_, | |
| 117 time_func_, | |
| 118 trusted_spdy_proxy_, | |
| 119 net_log.net_log())); | |
| 120 | |
| 121 new_session->InitializeWithSocket( | |
| 122 connection.Pass(), this, is_secure, certificate_error_code); | |
| 123 | |
| 124 base::WeakPtr<SpdySession> available_session = new_session->GetWeakPtr(); | |
| 125 sessions_.insert(new_session.release()); | |
| 126 MapKeyToAvailableSession(key, available_session); | |
| 127 | |
| 128 net_log.AddEvent( | |
| 129 NetLog::TYPE_SPDY_SESSION_POOL_IMPORTED_SESSION_FROM_SOCKET, | |
| 130 available_session->net_log().source().ToEventParametersCallback()); | |
| 131 | |
| 132 // Look up the IP address for this session so that we can match | |
| 133 // future sessions (potentially to different domains) which can | |
| 134 // potentially be pooled with this one. Because GetPeerAddress() | |
| 135 // reports the proxy's address instead of the origin server, check | |
| 136 // to see if this is a direct connection. | |
| 137 if (key.proxy_server().is_direct()) { | |
| 138 IPEndPoint address; | |
| 139 if (available_session->GetPeerAddress(&address) == OK) | |
| 140 aliases_[address] = key; | |
| 141 } | |
| 142 | |
| 143 return available_session; | |
| 144 } | |
| 145 | |
| 146 base::WeakPtr<SpdySession> SpdySessionPool::FindAvailableSession( | |
| 147 const SpdySessionKey& key, | |
| 148 const BoundNetLog& net_log) { | |
| 149 AvailableSessionMap::iterator it = LookupAvailableSessionByKey(key); | |
| 150 if (it != available_sessions_.end()) { | |
| 151 UMA_HISTOGRAM_ENUMERATION( | |
| 152 "Net.SpdySessionGet", FOUND_EXISTING, SPDY_SESSION_GET_MAX); | |
| 153 net_log.AddEvent( | |
| 154 NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION, | |
| 155 it->second->net_log().source().ToEventParametersCallback()); | |
| 156 return it->second; | |
| 157 } | |
| 158 | |
| 159 // Look up the key's from the resolver's cache. | |
| 160 net::HostResolver::RequestInfo resolve_info(key.host_port_pair()); | |
| 161 AddressList addresses; | |
| 162 int rv = resolver_->ResolveFromCache(resolve_info, &addresses, net_log); | |
| 163 DCHECK_NE(rv, ERR_IO_PENDING); | |
| 164 if (rv != OK) | |
| 165 return base::WeakPtr<SpdySession>(); | |
| 166 | |
| 167 // Check if we have a session through a domain alias. | |
| 168 for (AddressList::const_iterator address_it = addresses.begin(); | |
| 169 address_it != addresses.end(); | |
| 170 ++address_it) { | |
| 171 AliasMap::const_iterator alias_it = aliases_.find(*address_it); | |
| 172 if (alias_it == aliases_.end()) | |
| 173 continue; | |
| 174 | |
| 175 // We found an alias. | |
| 176 const SpdySessionKey& alias_key = alias_it->second; | |
| 177 | |
| 178 // We can reuse this session only if the proxy and privacy | |
| 179 // settings match. | |
| 180 if (!(alias_key.proxy_server() == key.proxy_server()) || | |
| 181 !(alias_key.privacy_mode() == key.privacy_mode())) | |
| 182 continue; | |
| 183 | |
| 184 AvailableSessionMap::iterator available_session_it = | |
| 185 LookupAvailableSessionByKey(alias_key); | |
| 186 if (available_session_it == available_sessions_.end()) { | |
| 187 NOTREACHED(); // It shouldn't be in the aliases table if we can't get it! | |
| 188 continue; | |
| 189 } | |
| 190 | |
| 191 const base::WeakPtr<SpdySession>& available_session = | |
| 192 available_session_it->second; | |
| 193 DCHECK(ContainsKey(sessions_, available_session.get())); | |
| 194 // If the session is a secure one, we need to verify that the | |
| 195 // server is authenticated to serve traffic for |host_port_proxy_pair| too. | |
| 196 if (!available_session->VerifyDomainAuthentication( | |
| 197 key.host_port_pair().host())) { | |
| 198 UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 0, 2); | |
| 199 continue; | |
| 200 } | |
| 201 | |
| 202 UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 1, 2); | |
| 203 UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", | |
| 204 FOUND_EXISTING_FROM_IP_POOL, | |
| 205 SPDY_SESSION_GET_MAX); | |
| 206 net_log.AddEvent( | |
| 207 NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION_FROM_IP_POOL, | |
| 208 available_session->net_log().source().ToEventParametersCallback()); | |
| 209 // Add this session to the map so that we can find it next time. | |
| 210 MapKeyToAvailableSession(key, available_session); | |
| 211 available_session->AddPooledAlias(key); | |
| 212 return available_session; | |
| 213 } | |
| 214 | |
| 215 return base::WeakPtr<SpdySession>(); | |
| 216 } | |
| 217 | |
| 218 void SpdySessionPool::MakeSessionUnavailable( | |
| 219 const base::WeakPtr<SpdySession>& available_session) { | |
| 220 UnmapKey(available_session->spdy_session_key()); | |
| 221 RemoveAliases(available_session->spdy_session_key()); | |
| 222 const std::set<SpdySessionKey>& aliases = available_session->pooled_aliases(); | |
| 223 for (std::set<SpdySessionKey>::const_iterator it = aliases.begin(); | |
| 224 it != aliases.end(); ++it) { | |
| 225 UnmapKey(*it); | |
| 226 RemoveAliases(*it); | |
| 227 } | |
| 228 DCHECK(!IsSessionAvailable(available_session)); | |
| 229 } | |
| 230 | |
| 231 void SpdySessionPool::RemoveUnavailableSession( | |
| 232 const base::WeakPtr<SpdySession>& unavailable_session) { | |
| 233 DCHECK(!IsSessionAvailable(unavailable_session)); | |
| 234 | |
| 235 unavailable_session->net_log().AddEvent( | |
| 236 NetLog::TYPE_SPDY_SESSION_POOL_REMOVE_SESSION, | |
| 237 unavailable_session->net_log().source().ToEventParametersCallback()); | |
| 238 | |
| 239 SessionSet::iterator it = sessions_.find(unavailable_session.get()); | |
| 240 CHECK(it != sessions_.end()); | |
| 241 scoped_ptr<SpdySession> owned_session(*it); | |
| 242 sessions_.erase(it); | |
| 243 } | |
| 244 | |
| 245 // Make a copy of |sessions_| in the Close* functions below to avoid | |
| 246 // reentrancy problems. Since arbitrary functions get called by close | |
| 247 // handlers, it doesn't suffice to simply increment the iterator | |
| 248 // before closing. | |
| 249 | |
| 250 void SpdySessionPool::CloseCurrentSessions(net::Error error) { | |
| 251 CloseCurrentSessionsHelper(error, "Closing current sessions.", | |
| 252 false /* idle_only */); | |
| 253 } | |
| 254 | |
| 255 void SpdySessionPool::CloseCurrentIdleSessions() { | |
| 256 CloseCurrentSessionsHelper(ERR_ABORTED, "Closing idle sessions.", | |
| 257 true /* idle_only */); | |
| 258 } | |
| 259 | |
| 260 void SpdySessionPool::CloseAllSessions() { | |
| 261 while (!available_sessions_.empty()) { | |
| 262 CloseCurrentSessionsHelper(ERR_ABORTED, "Closing all sessions.", | |
| 263 false /* idle_only */); | |
| 264 } | |
| 265 } | |
| 266 | |
| 267 base::Value* SpdySessionPool::SpdySessionPoolInfoToValue() const { | |
| 268 base::ListValue* list = new base::ListValue(); | |
| 269 | |
| 270 for (AvailableSessionMap::const_iterator it = available_sessions_.begin(); | |
| 271 it != available_sessions_.end(); ++it) { | |
| 272 // Only add the session if the key in the map matches the main | |
| 273 // host_port_proxy_pair (not an alias). | |
| 274 const SpdySessionKey& key = it->first; | |
| 275 const SpdySessionKey& session_key = it->second->spdy_session_key(); | |
| 276 if (key.Equals(session_key)) | |
| 277 list->Append(it->second->GetInfoAsValue()); | |
| 278 } | |
| 279 return list; | |
| 280 } | |
| 281 | |
| 282 void SpdySessionPool::OnIPAddressChanged() { | |
| 283 WeakSessionList current_sessions = GetCurrentSessions(); | |
| 284 for (WeakSessionList::const_iterator it = current_sessions.begin(); | |
| 285 it != current_sessions.end(); ++it) { | |
| 286 if (!*it) | |
| 287 continue; | |
| 288 | |
| 289 // For OSs that terminate TCP connections upon relevant network changes, | |
| 290 // attempt to preserve active streams by marking all sessions as going | |
| 291 // away, rather than explicitly closing them. Streams may still fail due | |
| 292 // to a generated TCP reset. | |
| 293 #if defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_IOS) | |
| 294 (*it)->MakeUnavailable(); | |
| 295 (*it)->StartGoingAway(kLastStreamId, ERR_NETWORK_CHANGED); | |
| 296 (*it)->MaybeFinishGoingAway(); | |
| 297 #else | |
| 298 (*it)->CloseSessionOnError(ERR_NETWORK_CHANGED, | |
| 299 "Closing current sessions."); | |
| 300 DCHECK((*it)->IsDraining()); | |
| 301 #endif // defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_IOS) | |
| 302 DCHECK(!IsSessionAvailable(*it)); | |
| 303 } | |
| 304 http_server_properties_->ClearAllSpdySettings(); | |
| 305 } | |
| 306 | |
| 307 void SpdySessionPool::OnSSLConfigChanged() { | |
| 308 CloseCurrentSessions(ERR_NETWORK_CHANGED); | |
| 309 } | |
| 310 | |
| 311 void SpdySessionPool::OnCertAdded(const X509Certificate* cert) { | |
| 312 CloseCurrentSessions(ERR_CERT_DATABASE_CHANGED); | |
| 313 } | |
| 314 | |
| 315 void SpdySessionPool::OnCACertChanged(const X509Certificate* cert) { | |
| 316 // Per wtc, we actually only need to CloseCurrentSessions when trust is | |
| 317 // reduced. CloseCurrentSessions now because OnCACertChanged does not | |
| 318 // tell us this. | |
| 319 // See comments in ClientSocketPoolManager::OnCACertChanged. | |
| 320 CloseCurrentSessions(ERR_CERT_DATABASE_CHANGED); | |
| 321 } | |
| 322 | |
| 323 bool SpdySessionPool::IsSessionAvailable( | |
| 324 const base::WeakPtr<SpdySession>& session) const { | |
| 325 for (AvailableSessionMap::const_iterator it = available_sessions_.begin(); | |
| 326 it != available_sessions_.end(); ++it) { | |
| 327 if (it->second.get() == session.get()) | |
| 328 return true; | |
| 329 } | |
| 330 return false; | |
| 331 } | |
| 332 | |
| 333 const SpdySessionKey& SpdySessionPool::NormalizeListKey( | |
| 334 const SpdySessionKey& key) const { | |
| 335 if (!force_single_domain_) | |
| 336 return key; | |
| 337 | |
| 338 static SpdySessionKey* single_domain_key = NULL; | |
| 339 if (!single_domain_key) { | |
| 340 HostPortPair single_domain = HostPortPair("singledomain.com", 80); | |
| 341 single_domain_key = new SpdySessionKey(single_domain, | |
| 342 ProxyServer::Direct(), | |
| 343 PRIVACY_MODE_DISABLED); | |
| 344 } | |
| 345 return *single_domain_key; | |
| 346 } | |
| 347 | |
| 348 void SpdySessionPool::MapKeyToAvailableSession( | |
| 349 const SpdySessionKey& key, | |
| 350 const base::WeakPtr<SpdySession>& session) { | |
| 351 DCHECK(ContainsKey(sessions_, session.get())); | |
| 352 const SpdySessionKey& normalized_key = NormalizeListKey(key); | |
| 353 std::pair<AvailableSessionMap::iterator, bool> result = | |
| 354 available_sessions_.insert(std::make_pair(normalized_key, session)); | |
| 355 CHECK(result.second); | |
| 356 } | |
| 357 | |
| 358 SpdySessionPool::AvailableSessionMap::iterator | |
| 359 SpdySessionPool::LookupAvailableSessionByKey( | |
| 360 const SpdySessionKey& key) { | |
| 361 const SpdySessionKey& normalized_key = NormalizeListKey(key); | |
| 362 return available_sessions_.find(normalized_key); | |
| 363 } | |
| 364 | |
| 365 void SpdySessionPool::UnmapKey(const SpdySessionKey& key) { | |
| 366 AvailableSessionMap::iterator it = LookupAvailableSessionByKey(key); | |
| 367 CHECK(it != available_sessions_.end()); | |
| 368 available_sessions_.erase(it); | |
| 369 } | |
| 370 | |
| 371 void SpdySessionPool::RemoveAliases(const SpdySessionKey& key) { | |
| 372 // Walk the aliases map, find references to this pair. | |
| 373 // TODO(mbelshe): Figure out if this is too expensive. | |
| 374 for (AliasMap::iterator it = aliases_.begin(); it != aliases_.end(); ) { | |
| 375 if (it->second.Equals(key)) { | |
| 376 AliasMap::iterator old_it = it; | |
| 377 ++it; | |
| 378 aliases_.erase(old_it); | |
| 379 } else { | |
| 380 ++it; | |
| 381 } | |
| 382 } | |
| 383 } | |
| 384 | |
| 385 SpdySessionPool::WeakSessionList SpdySessionPool::GetCurrentSessions() const { | |
| 386 WeakSessionList current_sessions; | |
| 387 for (SessionSet::const_iterator it = sessions_.begin(); | |
| 388 it != sessions_.end(); ++it) { | |
| 389 current_sessions.push_back((*it)->GetWeakPtr()); | |
| 390 } | |
| 391 return current_sessions; | |
| 392 } | |
| 393 | |
| 394 void SpdySessionPool::CloseCurrentSessionsHelper( | |
| 395 Error error, | |
| 396 const std::string& description, | |
| 397 bool idle_only) { | |
| 398 WeakSessionList current_sessions = GetCurrentSessions(); | |
| 399 for (WeakSessionList::const_iterator it = current_sessions.begin(); | |
| 400 it != current_sessions.end(); ++it) { | |
| 401 if (!*it) | |
| 402 continue; | |
| 403 | |
| 404 if (idle_only && (*it)->is_active()) | |
| 405 continue; | |
| 406 | |
| 407 (*it)->CloseSessionOnError(error, description); | |
| 408 DCHECK(!IsSessionAvailable(*it)); | |
| 409 } | |
| 410 } | |
| 411 | |
| 412 } // namespace net | |
| OLD | NEW |