1 // Copyright (c) 2011 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/base/sys_addrinfo.h" 12 #include "net/http/http_network_session.h" 13 #include "net/spdy/spdy_session.h" 14 15 16 namespace net { 17 18 namespace { 19 20 enum 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 bool HostPortProxyPairsAreEqual(const HostPortProxyPair& a, 29 const HostPortProxyPair& b) { 30 return a.first.Equals(b.first) && a.second == b.second; 31 } 32 33 } 34 35 // The maximum number of sessions to open to a single domain. 36 static const size_t kMaxSessionsPerDomain = 1; 37 38 size_t SpdySessionPool::g_max_sessions_per_domain = kMaxSessionsPerDomain; 39 bool SpdySessionPool::g_force_single_domain = false; 40 bool SpdySessionPool::g_enable_ip_pooling = true; 41 42 SpdySessionPool::SpdySessionPool(HostResolver* resolver, 43 SSLConfigService* ssl_config_service) 44 : ssl_config_service_(ssl_config_service), 45 resolver_(resolver) { 46 NetworkChangeNotifier::AddIPAddressObserver(this); 47 if (ssl_config_service_) 48 ssl_config_service_->AddObserver(this); 49 CertDatabase::AddObserver(this); 50 } 51 52 SpdySessionPool::~SpdySessionPool() { 53 CloseAllSessions(); 54 55 if (ssl_config_service_) 56 ssl_config_service_->RemoveObserver(this); 57 NetworkChangeNotifier::RemoveIPAddressObserver(this); 58 CertDatabase::RemoveObserver(this); 59 } 60 61 scoped_refptr<SpdySession> SpdySessionPool::Get( 62 const HostPortProxyPair& host_port_proxy_pair, 63 const BoundNetLog& net_log) { 64 scoped_refptr<SpdySession> spdy_session; 65 SpdySessionList* list = GetSessionList(host_port_proxy_pair); 66 if (!list) { 67 // Check if we have a Session through a domain alias. 68 spdy_session = GetFromAlias(host_port_proxy_pair, net_log, true); 69 if (spdy_session) { 70 UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", 71 FOUND_EXISTING_FROM_IP_POOL, 72 SPDY_SESSION_GET_MAX); 73 net_log.AddEvent( 74 NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION_FROM_IP_POOL, 75 make_scoped_refptr(new NetLogSourceParameter( 76 "session", spdy_session->net_log().source()))); 77 return spdy_session; 78 } 79 list = AddSessionList(host_port_proxy_pair); 80 } 81 82 DCHECK(list); 83 if (list->size() && list->size() == g_max_sessions_per_domain) { 84 UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", 85 FOUND_EXISTING, 86 SPDY_SESSION_GET_MAX); 87 spdy_session = GetExistingSession(list, net_log); 88 net_log.AddEvent( 89 NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION, 90 make_scoped_refptr(new NetLogSourceParameter( 91 "session", spdy_session->net_log().source()))); 92 return spdy_session; 93 } 94 95 spdy_session = new SpdySession(host_port_proxy_pair, this, &spdy_settings_, 96 net_log.net_log()); 97 UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", 98 CREATED_NEW, 99 SPDY_SESSION_GET_MAX); 100 list->push_back(spdy_session); 101 net_log.AddEvent( 102 NetLog::TYPE_SPDY_SESSION_POOL_CREATED_NEW_SESSION, 103 make_scoped_refptr(new NetLogSourceParameter( 104 "session", spdy_session->net_log().source()))); 105 DCHECK_LE(list->size(), g_max_sessions_per_domain); 106 return spdy_session; 107 } 108 109 net::Error SpdySessionPool::GetSpdySessionFromSocket( 110 const HostPortProxyPair& host_port_proxy_pair, 111 ClientSocketHandle* connection, 112 const BoundNetLog& net_log, 113 int certificate_error_code, 114 scoped_refptr<SpdySession>* spdy_session, 115 bool is_secure) { 116 UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", 117 IMPORTED_FROM_SOCKET, 118 SPDY_SESSION_GET_MAX); 119 // Create the SPDY session and add it to the pool. 120 *spdy_session = new SpdySession(host_port_proxy_pair, this, &spdy_settings_, 121 net_log.net_log()); 122 SpdySessionList* list = GetSessionList(host_port_proxy_pair); 123 if (!list) 124 list = AddSessionList(host_port_proxy_pair); 125 DCHECK(list->empty()); 126 list->push_back(*spdy_session); 127 128 net_log.AddEvent( 129 NetLog::TYPE_SPDY_SESSION_POOL_IMPORTED_SESSION_FROM_SOCKET, 130 make_scoped_refptr(new NetLogSourceParameter( 131 "session", (*spdy_session)->net_log().source()))); 132 133 // Now we can initialize the session with the SSL socket. 134 return (*spdy_session)->InitializeWithSocket(connection, is_secure, 135 certificate_error_code); 136 } 137 138 bool SpdySessionPool::HasSession( 139 const HostPortProxyPair& host_port_proxy_pair) const { 140 if (GetSessionList(host_port_proxy_pair)) 141 return true; 142 143 // Check if we have a session via an alias. 144 scoped_refptr<SpdySession> spdy_session = 145 GetFromAlias(host_port_proxy_pair, BoundNetLog(), false); 146 return spdy_session.get() != NULL; 147 } 148 149 void SpdySessionPool::Remove(const scoped_refptr<SpdySession>& session) { 150 SpdySessionList* list = GetSessionList(session->host_port_proxy_pair()); 151 DCHECK(list); // We really shouldn't remove if we've already been removed. 152 if (!list) 153 return; 154 list->remove(session); 155 session->net_log().AddEvent( 156 NetLog::TYPE_SPDY_SESSION_POOL_REMOVE_SESSION, 157 make_scoped_refptr(new NetLogSourceParameter( 158 "session", session->net_log().source()))); 159 if (list->empty()) 160 RemoveSessionList(session->host_port_proxy_pair()); 161 } 162 163 Value* SpdySessionPool::SpdySessionPoolInfoToValue() const { 164 ListValue* list = new ListValue(); 165 166 SpdySessionsMap::const_iterator spdy_session_pool_it = sessions_.begin(); 167 for (SpdySessionsMap::const_iterator it = sessions_.begin(); 168 it != sessions_.end(); ++it) { 169 SpdySessionList* sessions = it->second; 170 for (SpdySessionList::const_iterator session = sessions->begin(); 171 session != sessions->end(); ++session) { 172 list->Append(session->get()->GetInfoAsValue()); 173 } 174 } 175 return list; 176 } 177 178 void SpdySessionPool::OnIPAddressChanged() { 179 CloseCurrentSessions(); 180 } 181 182 void SpdySessionPool::OnSSLConfigChanged() { 183 CloseCurrentSessions(); 184 } 185 186 scoped_refptr<SpdySession> SpdySessionPool::GetExistingSession( 187 SpdySessionList* list, 188 const BoundNetLog& net_log) const { 189 DCHECK(list); 190 DCHECK_LT(0u, list->size()); 191 scoped_refptr<SpdySession> spdy_session = list->front(); 192 if (list->size() > 1) { 193 list->pop_front(); // Rotate the list. 194 list->push_back(spdy_session); 195 } 196 197 return spdy_session; 198 } 199 200 scoped_refptr<SpdySession> SpdySessionPool::GetFromAlias( 201 const HostPortProxyPair& host_port_proxy_pair, 202 const BoundNetLog& net_log, 203 bool record_histograms) const { 204 // We should only be checking aliases when there is no direct session. 205 DCHECK(!GetSessionList(host_port_proxy_pair)); 206 207 if (!g_enable_ip_pooling) 208 return NULL; 209 210 AddressList addresses; 211 if (!LookupAddresses(host_port_proxy_pair, &addresses)) 212 return NULL; 213 const addrinfo* address = addresses.head(); 214 while (address) { 215 IPEndPoint endpoint; 216 endpoint.FromSockAddr(address->ai_addr, address->ai_addrlen); 217 address = address->ai_next; 218 219 SpdyAliasMap::const_iterator it = aliases_.find(endpoint); 220 if (it == aliases_.end()) 221 continue; 222 223 // We found an alias. 224 const HostPortProxyPair& alias_pair = it->second; 225 226 // If the proxy settings match, we can reuse this session. 227 if (!(alias_pair.second == host_port_proxy_pair.second)) 228 continue; 229 230 SpdySessionList* list = GetSessionList(alias_pair); 231 if (!list) { 232 NOTREACHED(); // It shouldn't be in the aliases table if we can't get it! 233 continue; 234 } 235 236 scoped_refptr<SpdySession> spdy_session = GetExistingSession(list, net_log); 237 // If the SPDY session is a secure one, we need to verify that the server 238 // is authenticated to serve traffic for |host_port_proxy_pair| too. 239 if (!spdy_session->VerifyDomainAuthentication( 240 host_port_proxy_pair.first.host())) { 241 if (record_histograms) 242 UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 0, 2); 243 continue; 244 } 245 if (record_histograms) 246 UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 1, 2); 247 return spdy_session; 248 } 249 return NULL; 250 } 251 252 void SpdySessionPool::OnUserCertAdded(const X509Certificate* cert) { 253 CloseCurrentSessions(); 254 } 255 256 void SpdySessionPool::OnCertTrustChanged(const X509Certificate* cert) { 257 // Per wtc, we actually only need to CloseCurrentSessions when trust is 258 // reduced. CloseCurrentSessions now because OnCertTrustChanged does not 259 // tell us this. 260 // See comments in ClientSocketPoolManager::OnCertTrustChanged. 261 CloseCurrentSessions(); 262 } 263 264 const HostPortProxyPair& SpdySessionPool::NormalizeListPair( 265 const HostPortProxyPair& host_port_proxy_pair) const { 266 if (!g_force_single_domain) 267 return host_port_proxy_pair; 268 269 static HostPortProxyPair* single_domain_pair = NULL; 270 if (!single_domain_pair) { 271 HostPortPair single_domain = HostPortPair("singledomain.com", 80); 272 single_domain_pair = new HostPortProxyPair(single_domain, 273 ProxyServer::Direct()); 274 } 275 return *single_domain_pair; 276 } 277 278 SpdySessionPool::SpdySessionList* 279 SpdySessionPool::AddSessionList( 280 const HostPortProxyPair& host_port_proxy_pair) { 281 const HostPortProxyPair& pair = NormalizeListPair(host_port_proxy_pair); 282 DCHECK(sessions_.find(pair) == sessions_.end()); 283 SpdySessionPool::SpdySessionList* list = new SpdySessionList(); 284 sessions_[pair] = list; 285 286 // We have a new session. Lookup the IP addresses for this session so that 287 // we can match future Sessions (potentially to different domains) which can 288 // potentially be pooled with this one. 289 if (g_enable_ip_pooling) { 290 AddressList addresses; 291 if (LookupAddresses(host_port_proxy_pair, &addresses)) 292 AddAliases(addresses, host_port_proxy_pair); 293 } 294 295 return list; 296 } 297 298 SpdySessionPool::SpdySessionList* 299 SpdySessionPool::GetSessionList( 300 const HostPortProxyPair& host_port_proxy_pair) const { 301 const HostPortProxyPair& pair = NormalizeListPair(host_port_proxy_pair); 302 SpdySessionsMap::const_iterator it = sessions_.find(pair); 303 if (it != sessions_.end()) 304 return it->second; 305 return NULL; 306 } 307 308 void SpdySessionPool::RemoveSessionList( 309 const HostPortProxyPair& host_port_proxy_pair) { 310 const HostPortProxyPair& pair = NormalizeListPair(host_port_proxy_pair); 311 SpdySessionList* list = GetSessionList(pair); 312 if (list) { 313 delete list; 314 sessions_.erase(pair); 315 } else { 316 DCHECK(false) << "removing orphaned session list"; 317 } 318 RemoveAliases(host_port_proxy_pair); 319 } 320 321 bool SpdySessionPool::LookupAddresses(const HostPortProxyPair& pair, 322 AddressList* addresses) const { 323 net::HostResolver::RequestInfo resolve_info(pair.first); 324 resolve_info.set_only_use_cached_response(true); 325 int rv = resolver_->Resolve(resolve_info, 326 addresses, 327 NULL, 328 NULL, 329 net::BoundNetLog()); 330 DCHECK_NE(ERR_IO_PENDING, rv); 331 return rv == OK; 332 } 333 334 void SpdySessionPool::AddAliases(const AddressList& addresses, 335 const HostPortProxyPair& pair) { 336 // Note: it is possible to think of strange overlapping sets of ip addresses 337 // for hosts such that a new session can override the alias for an IP 338 // address that was previously aliased to a different host. This is probably 339 // undesirable, but seemingly unlikely and complicated to fix. 340 // Example: 341 // host1 = 1.1.1.1, 1.1.1.4 342 // host2 = 1.1.1.4, 1.1.1.5 343 // host3 = 1.1.1.3, 1.1.1.5 344 // Creating session1 (to host1), creates an alias for host2 to host1. 345 // Creating session2 (to host3), overrides the alias for host2 to host3. 346 347 const addrinfo* address = addresses.head(); 348 while (address) { 349 IPEndPoint endpoint; 350 endpoint.FromSockAddr(address->ai_addr, address->ai_addrlen); 351 aliases_[endpoint] = pair; 352 address = address->ai_next; 353 } 354 } 355 356 void SpdySessionPool::RemoveAliases(const HostPortProxyPair& pair) { 357 // Walk the aliases map, find references to this pair. 358 // TODO(mbelshe): Figure out if this is too expensive. 359 SpdyAliasMap::iterator alias_it = aliases_.begin(); 360 while (alias_it != aliases_.end()) { 361 if (HostPortProxyPairsAreEqual(alias_it->second, pair)) { 362 aliases_.erase(alias_it); 363 alias_it = aliases_.begin(); // Iterator was invalidated. 364 continue; 365 } 366 ++alias_it; 367 } 368 } 369 370 void SpdySessionPool::CloseAllSessions() { 371 while (!sessions_.empty()) { 372 SpdySessionList* list = sessions_.begin()->second; 373 CHECK(list); 374 const scoped_refptr<SpdySession>& session = list->front(); 375 CHECK(session); 376 // This call takes care of removing the session from the pool, as well as 377 // removing the session list if the list is empty. 378 session->CloseSessionOnError(net::ERR_ABORTED, true); 379 } 380 } 381 382 void SpdySessionPool::CloseCurrentSessions() { 383 SpdySessionsMap old_map; 384 old_map.swap(sessions_); 385 for (SpdySessionsMap::const_iterator it = old_map.begin(); 386 it != old_map.end(); ++it) { 387 SpdySessionList* list = it->second; 388 CHECK(list); 389 const scoped_refptr<SpdySession>& session = list->front(); 390 CHECK(session); 391 session->set_spdy_session_pool(NULL); 392 } 393 394 while (!old_map.empty()) { 395 SpdySessionList* list = old_map.begin()->second; 396 CHECK(list); 397 const scoped_refptr<SpdySession>& session = list->front(); 398 CHECK(session); 399 session->CloseSessionOnError(net::ERR_ABORTED, false); 400 list->pop_front(); 401 if (list->empty()) { 402 delete list; 403 RemoveAliases(old_map.begin()->first); 404 old_map.erase(old_map.begin()->first); 405 } 406 } 407 DCHECK(sessions_.empty()); 408 DCHECK(aliases_.empty()); 409 } 410 411 void SpdySessionPool::CloseIdleSessions() { 412 SpdySessionsMap::const_iterator map_it = sessions_.begin(); 413 while (map_it != sessions_.end()) { 414 SpdySessionList* list = map_it->second; 415 ++map_it; 416 CHECK(list); 417 418 // Assumes there is only 1 element in the list 419 SpdySessionList::iterator session_it = list->begin(); 420 const scoped_refptr<SpdySession>& session = *session_it; 421 CHECK(session); 422 if (!session->is_active()) 423 session->CloseSessionOnError(net::ERR_ABORTED, true); 424 } 425 } 426 427 } // namespace net 428