| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/spdy/spdy_session_pool.h" |
| |
| #include "base/logging.h" |
| #include "base/metrics/histogram.h" |
| #include "base/values.h" |
| #include "net/base/address_list.h" |
| #include "net/base/sys_addrinfo.h" |
| #include "net/http/http_network_session.h" |
| #include "net/spdy/spdy_session.h" |
| |
| |
| namespace net { |
| |
| namespace { |
| |
| enum SpdySessionGetTypes { |
| CREATED_NEW = 0, |
| FOUND_EXISTING = 1, |
| FOUND_EXISTING_FROM_IP_POOL = 2, |
| IMPORTED_FROM_SOCKET = 3, |
| SPDY_SESSION_GET_MAX = 4 |
| }; |
| |
| bool HostPortProxyPairsAreEqual(const HostPortProxyPair& a, |
| const HostPortProxyPair& b) { |
| return a.first.Equals(b.first) && a.second == b.second; |
| } |
| |
| } |
| |
| // The maximum number of sessions to open to a single domain. |
| static const size_t kMaxSessionsPerDomain = 1; |
| |
| size_t SpdySessionPool::g_max_sessions_per_domain = kMaxSessionsPerDomain; |
| bool SpdySessionPool::g_force_single_domain = false; |
| bool SpdySessionPool::g_enable_ip_pooling = true; |
| |
| SpdySessionPool::SpdySessionPool(HostResolver* resolver, |
| SSLConfigService* ssl_config_service) |
| : ssl_config_service_(ssl_config_service), |
| resolver_(resolver) { |
| NetworkChangeNotifier::AddIPAddressObserver(this); |
| if (ssl_config_service_) |
| ssl_config_service_->AddObserver(this); |
| CertDatabase::AddObserver(this); |
| } |
| |
| SpdySessionPool::~SpdySessionPool() { |
| CloseAllSessions(); |
| |
| if (ssl_config_service_) |
| ssl_config_service_->RemoveObserver(this); |
| NetworkChangeNotifier::RemoveIPAddressObserver(this); |
| CertDatabase::RemoveObserver(this); |
| } |
| |
| scoped_refptr<SpdySession> SpdySessionPool::Get( |
| const HostPortProxyPair& host_port_proxy_pair, |
| const BoundNetLog& net_log) { |
| scoped_refptr<SpdySession> spdy_session; |
| SpdySessionList* list = GetSessionList(host_port_proxy_pair); |
| if (!list) { |
| // Check if we have a Session through a domain alias. |
| spdy_session = GetFromAlias(host_port_proxy_pair, net_log, true); |
| if (spdy_session) { |
| UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", |
| FOUND_EXISTING_FROM_IP_POOL, |
| SPDY_SESSION_GET_MAX); |
| net_log.AddEvent( |
| NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION_FROM_IP_POOL, |
| make_scoped_refptr(new NetLogSourceParameter( |
| "session", spdy_session->net_log().source()))); |
| return spdy_session; |
| } |
| list = AddSessionList(host_port_proxy_pair); |
| } |
| |
| DCHECK(list); |
| if (list->size() && list->size() == g_max_sessions_per_domain) { |
| UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", |
| FOUND_EXISTING, |
| SPDY_SESSION_GET_MAX); |
| spdy_session = GetExistingSession(list, net_log); |
| net_log.AddEvent( |
| NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION, |
| make_scoped_refptr(new NetLogSourceParameter( |
| "session", spdy_session->net_log().source()))); |
| return spdy_session; |
| } |
| |
| spdy_session = new SpdySession(host_port_proxy_pair, this, &spdy_settings_, |
| net_log.net_log()); |
| UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", |
| CREATED_NEW, |
| SPDY_SESSION_GET_MAX); |
| list->push_back(spdy_session); |
| net_log.AddEvent( |
| NetLog::TYPE_SPDY_SESSION_POOL_CREATED_NEW_SESSION, |
| make_scoped_refptr(new NetLogSourceParameter( |
| "session", spdy_session->net_log().source()))); |
| DCHECK_LE(list->size(), g_max_sessions_per_domain); |
| return spdy_session; |
| } |
| |
| net::Error SpdySessionPool::GetSpdySessionFromSocket( |
| const HostPortProxyPair& host_port_proxy_pair, |
| ClientSocketHandle* connection, |
| const BoundNetLog& net_log, |
| int certificate_error_code, |
| scoped_refptr<SpdySession>* spdy_session, |
| bool is_secure) { |
| UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", |
| IMPORTED_FROM_SOCKET, |
| SPDY_SESSION_GET_MAX); |
| // Create the SPDY session and add it to the pool. |
| *spdy_session = new SpdySession(host_port_proxy_pair, this, &spdy_settings_, |
| net_log.net_log()); |
| SpdySessionList* list = GetSessionList(host_port_proxy_pair); |
| if (!list) |
| list = AddSessionList(host_port_proxy_pair); |
| DCHECK(list->empty()); |
| list->push_back(*spdy_session); |
| |
| net_log.AddEvent( |
| NetLog::TYPE_SPDY_SESSION_POOL_IMPORTED_SESSION_FROM_SOCKET, |
| make_scoped_refptr(new NetLogSourceParameter( |
| "session", (*spdy_session)->net_log().source()))); |
| |
| // Now we can initialize the session with the SSL socket. |
| return (*spdy_session)->InitializeWithSocket(connection, is_secure, |
| certificate_error_code); |
| } |
| |
| bool SpdySessionPool::HasSession( |
| const HostPortProxyPair& host_port_proxy_pair) const { |
| if (GetSessionList(host_port_proxy_pair)) |
| return true; |
| |
| // Check if we have a session via an alias. |
| scoped_refptr<SpdySession> spdy_session = |
| GetFromAlias(host_port_proxy_pair, BoundNetLog(), false); |
| return spdy_session.get() != NULL; |
| } |
| |
| void SpdySessionPool::Remove(const scoped_refptr<SpdySession>& session) { |
| SpdySessionList* list = GetSessionList(session->host_port_proxy_pair()); |
| DCHECK(list); // We really shouldn't remove if we've already been removed. |
| if (!list) |
| return; |
| list->remove(session); |
| session->net_log().AddEvent( |
| NetLog::TYPE_SPDY_SESSION_POOL_REMOVE_SESSION, |
| make_scoped_refptr(new NetLogSourceParameter( |
| "session", session->net_log().source()))); |
| if (list->empty()) |
| RemoveSessionList(session->host_port_proxy_pair()); |
| } |
| |
| Value* SpdySessionPool::SpdySessionPoolInfoToValue() const { |
| ListValue* list = new ListValue(); |
| |
| SpdySessionsMap::const_iterator spdy_session_pool_it = sessions_.begin(); |
| for (SpdySessionsMap::const_iterator it = sessions_.begin(); |
| it != sessions_.end(); ++it) { |
| SpdySessionList* sessions = it->second; |
| for (SpdySessionList::const_iterator session = sessions->begin(); |
| session != sessions->end(); ++session) { |
| list->Append(session->get()->GetInfoAsValue()); |
| } |
| } |
| return list; |
| } |
| |
| void SpdySessionPool::OnIPAddressChanged() { |
| CloseCurrentSessions(); |
| } |
| |
| void SpdySessionPool::OnSSLConfigChanged() { |
| CloseCurrentSessions(); |
| } |
| |
| scoped_refptr<SpdySession> SpdySessionPool::GetExistingSession( |
| SpdySessionList* list, |
| const BoundNetLog& net_log) const { |
| DCHECK(list); |
| DCHECK_LT(0u, list->size()); |
| scoped_refptr<SpdySession> spdy_session = list->front(); |
| if (list->size() > 1) { |
| list->pop_front(); // Rotate the list. |
| list->push_back(spdy_session); |
| } |
| |
| return spdy_session; |
| } |
| |
| scoped_refptr<SpdySession> SpdySessionPool::GetFromAlias( |
| const HostPortProxyPair& host_port_proxy_pair, |
| const BoundNetLog& net_log, |
| bool record_histograms) const { |
| // We should only be checking aliases when there is no direct session. |
| DCHECK(!GetSessionList(host_port_proxy_pair)); |
| |
| if (!g_enable_ip_pooling) |
| return NULL; |
| |
| AddressList addresses; |
| if (!LookupAddresses(host_port_proxy_pair, &addresses)) |
| return NULL; |
| const addrinfo* address = addresses.head(); |
| while (address) { |
| IPEndPoint endpoint; |
| endpoint.FromSockAddr(address->ai_addr, address->ai_addrlen); |
| address = address->ai_next; |
| |
| SpdyAliasMap::const_iterator it = aliases_.find(endpoint); |
| if (it == aliases_.end()) |
| continue; |
| |
| // We found an alias. |
| const HostPortProxyPair& alias_pair = it->second; |
| |
| // If the proxy settings match, we can reuse this session. |
| if (!(alias_pair.second == host_port_proxy_pair.second)) |
| continue; |
| |
| SpdySessionList* list = GetSessionList(alias_pair); |
| if (!list) { |
| NOTREACHED(); // It shouldn't be in the aliases table if we can't get it! |
| continue; |
| } |
| |
| scoped_refptr<SpdySession> spdy_session = GetExistingSession(list, net_log); |
| // If the SPDY session is a secure one, we need to verify that the server |
| // is authenticated to serve traffic for |host_port_proxy_pair| too. |
| if (!spdy_session->VerifyDomainAuthentication( |
| host_port_proxy_pair.first.host())) { |
| if (record_histograms) |
| UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 0, 2); |
| continue; |
| } |
| if (record_histograms) |
| UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 1, 2); |
| return spdy_session; |
| } |
| return NULL; |
| } |
| |
| void SpdySessionPool::OnUserCertAdded(const X509Certificate* cert) { |
| CloseCurrentSessions(); |
| } |
| |
| void SpdySessionPool::OnCertTrustChanged(const X509Certificate* cert) { |
| // Per wtc, we actually only need to CloseCurrentSessions when trust is |
| // reduced. CloseCurrentSessions now because OnCertTrustChanged does not |
| // tell us this. |
| // See comments in ClientSocketPoolManager::OnCertTrustChanged. |
| CloseCurrentSessions(); |
| } |
| |
| const HostPortProxyPair& SpdySessionPool::NormalizeListPair( |
| const HostPortProxyPair& host_port_proxy_pair) const { |
| if (!g_force_single_domain) |
| return host_port_proxy_pair; |
| |
| static HostPortProxyPair* single_domain_pair = NULL; |
| if (!single_domain_pair) { |
| HostPortPair single_domain = HostPortPair("singledomain.com", 80); |
| single_domain_pair = new HostPortProxyPair(single_domain, |
| ProxyServer::Direct()); |
| } |
| return *single_domain_pair; |
| } |
| |
| SpdySessionPool::SpdySessionList* |
| SpdySessionPool::AddSessionList( |
| const HostPortProxyPair& host_port_proxy_pair) { |
| const HostPortProxyPair& pair = NormalizeListPair(host_port_proxy_pair); |
| DCHECK(sessions_.find(pair) == sessions_.end()); |
| SpdySessionPool::SpdySessionList* list = new SpdySessionList(); |
| sessions_[pair] = list; |
| |
| // We have a new session. Lookup the IP addresses for this session so that |
| // we can match future Sessions (potentially to different domains) which can |
| // potentially be pooled with this one. |
| if (g_enable_ip_pooling) { |
| AddressList addresses; |
| if (LookupAddresses(host_port_proxy_pair, &addresses)) |
| AddAliases(addresses, host_port_proxy_pair); |
| } |
| |
| return list; |
| } |
| |
| SpdySessionPool::SpdySessionList* |
| SpdySessionPool::GetSessionList( |
| const HostPortProxyPair& host_port_proxy_pair) const { |
| const HostPortProxyPair& pair = NormalizeListPair(host_port_proxy_pair); |
| SpdySessionsMap::const_iterator it = sessions_.find(pair); |
| if (it != sessions_.end()) |
| return it->second; |
| return NULL; |
| } |
| |
| void SpdySessionPool::RemoveSessionList( |
| const HostPortProxyPair& host_port_proxy_pair) { |
| const HostPortProxyPair& pair = NormalizeListPair(host_port_proxy_pair); |
| SpdySessionList* list = GetSessionList(pair); |
| if (list) { |
| delete list; |
| sessions_.erase(pair); |
| } else { |
| DCHECK(false) << "removing orphaned session list"; |
| } |
| RemoveAliases(host_port_proxy_pair); |
| } |
| |
| bool SpdySessionPool::LookupAddresses(const HostPortProxyPair& pair, |
| AddressList* addresses) const { |
| net::HostResolver::RequestInfo resolve_info(pair.first); |
| resolve_info.set_only_use_cached_response(true); |
| int rv = resolver_->Resolve(resolve_info, |
| addresses, |
| NULL, |
| NULL, |
| net::BoundNetLog()); |
| DCHECK_NE(ERR_IO_PENDING, rv); |
| return rv == OK; |
| } |
| |
| void SpdySessionPool::AddAliases(const AddressList& addresses, |
| const HostPortProxyPair& pair) { |
| // Note: it is possible to think of strange overlapping sets of ip addresses |
| // for hosts such that a new session can override the alias for an IP |
| // address that was previously aliased to a different host. This is probably |
| // undesirable, but seemingly unlikely and complicated to fix. |
| // Example: |
| // host1 = 1.1.1.1, 1.1.1.4 |
| // host2 = 1.1.1.4, 1.1.1.5 |
| // host3 = 1.1.1.3, 1.1.1.5 |
| // Creating session1 (to host1), creates an alias for host2 to host1. |
| // Creating session2 (to host3), overrides the alias for host2 to host3. |
| |
| const addrinfo* address = addresses.head(); |
| while (address) { |
| IPEndPoint endpoint; |
| endpoint.FromSockAddr(address->ai_addr, address->ai_addrlen); |
| aliases_[endpoint] = pair; |
| address = address->ai_next; |
| } |
| } |
| |
| void SpdySessionPool::RemoveAliases(const HostPortProxyPair& pair) { |
| // Walk the aliases map, find references to this pair. |
| // TODO(mbelshe): Figure out if this is too expensive. |
| SpdyAliasMap::iterator alias_it = aliases_.begin(); |
| while (alias_it != aliases_.end()) { |
| if (HostPortProxyPairsAreEqual(alias_it->second, pair)) { |
| aliases_.erase(alias_it); |
| alias_it = aliases_.begin(); // Iterator was invalidated. |
| continue; |
| } |
| ++alias_it; |
| } |
| } |
| |
| void SpdySessionPool::CloseAllSessions() { |
| while (!sessions_.empty()) { |
| SpdySessionList* list = sessions_.begin()->second; |
| CHECK(list); |
| const scoped_refptr<SpdySession>& session = list->front(); |
| CHECK(session); |
| // This call takes care of removing the session from the pool, as well as |
| // removing the session list if the list is empty. |
| session->CloseSessionOnError(net::ERR_ABORTED, true); |
| } |
| } |
| |
| void SpdySessionPool::CloseCurrentSessions() { |
| SpdySessionsMap old_map; |
| old_map.swap(sessions_); |
| for (SpdySessionsMap::const_iterator it = old_map.begin(); |
| it != old_map.end(); ++it) { |
| SpdySessionList* list = it->second; |
| CHECK(list); |
| const scoped_refptr<SpdySession>& session = list->front(); |
| CHECK(session); |
| session->set_spdy_session_pool(NULL); |
| } |
| |
| while (!old_map.empty()) { |
| SpdySessionList* list = old_map.begin()->second; |
| CHECK(list); |
| const scoped_refptr<SpdySession>& session = list->front(); |
| CHECK(session); |
| session->CloseSessionOnError(net::ERR_ABORTED, false); |
| list->pop_front(); |
| if (list->empty()) { |
| delete list; |
| RemoveAliases(old_map.begin()->first); |
| old_map.erase(old_map.begin()->first); |
| } |
| } |
| DCHECK(sessions_.empty()); |
| DCHECK(aliases_.empty()); |
| } |
| |
| void SpdySessionPool::CloseIdleSessions() { |
| SpdySessionsMap::const_iterator map_it = sessions_.begin(); |
| while (map_it != sessions_.end()) { |
| SpdySessionList* list = map_it->second; |
| ++map_it; |
| CHECK(list); |
| |
| // Assumes there is only 1 element in the list |
| SpdySessionList::iterator session_it = list->begin(); |
| const scoped_refptr<SpdySession>& session = *session_it; |
| CHECK(session); |
| if (!session->is_active()) |
| session->CloseSessionOnError(net::ERR_ABORTED, true); |
| } |
| } |
| |
| } // namespace net |