blob: b967a463caa3fde9a1e28c7e84f1fbafe7a23f82 [file] [log] [blame]
// Copyright (c) 2012 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/callback.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/values.h"
#include "net/base/address_list.h"
#include "net/http/http_network_session.h"
#include "net/http/http_server_properties.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;
SpdySessionPool::SpdySessionPool(
HostResolver* resolver,
SSLConfigService* ssl_config_service,
HttpServerProperties* http_server_properties,
size_t max_sessions_per_domain,
bool force_single_domain,
bool enable_ip_pooling,
bool enable_credential_frames,
bool enable_compression,
bool enable_ping_based_connection_checking,
NextProto default_protocol,
size_t initial_recv_window_size,
size_t initial_max_concurrent_streams,
size_t max_concurrent_streams_limit,
SpdySessionPool::TimeFunc time_func,
const std::string& trusted_spdy_proxy)
: http_server_properties_(http_server_properties),
ssl_config_service_(ssl_config_service),
resolver_(resolver),
verify_domain_authentication_(true),
enable_sending_initial_settings_(true),
max_sessions_per_domain_(max_sessions_per_domain == 0 ?
kMaxSessionsPerDomain :
max_sessions_per_domain),
force_single_domain_(force_single_domain),
enable_ip_pooling_(enable_ip_pooling),
enable_credential_frames_(enable_credential_frames),
enable_compression_(enable_compression),
enable_ping_based_connection_checking_(
enable_ping_based_connection_checking),
default_protocol_(default_protocol),
initial_recv_window_size_(initial_recv_window_size),
initial_max_concurrent_streams_(initial_max_concurrent_streams),
max_concurrent_streams_limit_(max_concurrent_streams_limit),
time_func_(time_func),
trusted_spdy_proxy_(
HostPortPair::FromString(trusted_spdy_proxy)) {
NetworkChangeNotifier::AddIPAddressObserver(this);
if (ssl_config_service_)
ssl_config_service_->AddObserver(this);
CertDatabase::GetInstance()->AddObserver(this);
}
SpdySessionPool::~SpdySessionPool() {
CloseAllSessions();
if (ssl_config_service_)
ssl_config_service_->RemoveObserver(this);
NetworkChangeNotifier::RemoveIPAddressObserver(this);
CertDatabase::GetInstance()->RemoveObserver(this);
}
scoped_refptr<SpdySession> SpdySessionPool::Get(
const HostPortProxyPair& host_port_proxy_pair,
const BoundNetLog& net_log) {
return GetInternal(host_port_proxy_pair, net_log, false);
}
scoped_refptr<SpdySession> SpdySessionPool::GetIfExists(
const HostPortProxyPair& host_port_proxy_pair,
const BoundNetLog& net_log) {
return GetInternal(host_port_proxy_pair, net_log, true);
}
scoped_refptr<SpdySession> SpdySessionPool::GetInternal(
const HostPortProxyPair& host_port_proxy_pair,
const BoundNetLog& net_log,
bool only_use_existing_sessions) {
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,
spdy_session->net_log().source().ToEventParametersCallback());
// Add this session to the map so that we can find it next time.
list = AddSessionList(host_port_proxy_pair);
list->push_back(spdy_session);
spdy_session->AddPooledAlias(host_port_proxy_pair);
return spdy_session;
} else if (only_use_existing_sessions) {
return NULL;
}
list = AddSessionList(host_port_proxy_pair);
}
DCHECK(list);
if (list->size() && list->size() == 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,
spdy_session->net_log().source().ToEventParametersCallback());
return spdy_session;
}
DCHECK(!only_use_existing_sessions);
spdy_session = new SpdySession(host_port_proxy_pair, this,
http_server_properties_,
verify_domain_authentication_,
enable_sending_initial_settings_,
enable_credential_frames_,
enable_compression_,
enable_ping_based_connection_checking_,
default_protocol_,
initial_recv_window_size_,
initial_max_concurrent_streams_,
max_concurrent_streams_limit_,
time_func_,
trusted_spdy_proxy_,
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,
spdy_session->net_log().source().ToEventParametersCallback());
DCHECK_LE(list->size(), 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,
http_server_properties_,
verify_domain_authentication_,
enable_sending_initial_settings_,
enable_credential_frames_,
enable_compression_,
enable_ping_based_connection_checking_,
default_protocol_,
initial_recv_window_size_,
initial_max_concurrent_streams_,
max_concurrent_streams_limit_,
time_func_,
trusted_spdy_proxy_,
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,
(*spdy_session)->net_log().source().ToEventParametersCallback());
// We have a new session. Lookup the IP address for this session so that we
// can match future Sessions (potentially to different domains) which can
// potentially be pooled with this one. Because GetPeerAddress() reports the
// proxy's address instead of the origin server, check to see if this is a
// direct connection.
if (enable_ip_pooling_ && host_port_proxy_pair.second.is_direct()) {
IPEndPoint address;
if (connection->socket()->GetPeerAddress(&address) == OK)
AddAlias(address, host_port_proxy_pair);
}
// 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) {
bool ok = RemoveFromSessionList(session, session->host_port_proxy_pair());
DCHECK(ok);
session->net_log().AddEvent(
NetLog::TYPE_SPDY_SESSION_POOL_REMOVE_SESSION,
session->net_log().source().ToEventParametersCallback());
const std::set<HostPortProxyPair>& aliases = session->pooled_aliases();
for (std::set<HostPortProxyPair>::const_iterator it = aliases.begin();
it != aliases.end(); ++it) {
ok = RemoveFromSessionList(session, *it);
DCHECK(ok);
}
}
bool SpdySessionPool::RemoveFromSessionList(
const scoped_refptr<SpdySession>& session,
const HostPortProxyPair& pair) {
SpdySessionList* list = GetSessionList(pair);
if (!list)
return false;
list->remove(session);
if (list->empty())
RemoveSessionList(pair);
return true;
}
Value* SpdySessionPool::SpdySessionPoolInfoToValue() const {
ListValue* list = new ListValue();
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) {
// Only add the session if the key in the map matches the main
// host_port_proxy_pair (not an alias).
const HostPortProxyPair& key = it->first;
const HostPortProxyPair& pair = session->get()->host_port_proxy_pair();
if (key.first.Equals(pair.first) && key.second == pair.second)
list->Append(session->get()->GetInfoAsValue());
}
}
return list;
}
void SpdySessionPool::OnIPAddressChanged() {
CloseCurrentSessions(ERR_NETWORK_CHANGED);
http_server_properties_->ClearSpdySettings();
}
void SpdySessionPool::OnSSLConfigChanged() {
CloseCurrentSessions(ERR_NETWORK_CHANGED);
}
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 (!enable_ip_pooling_)
return NULL;
AddressList addresses;
if (!LookupAddresses(host_port_proxy_pair, net_log, &addresses))
return NULL;
for (AddressList::const_iterator iter = addresses.begin();
iter != addresses.end();
++iter) {
SpdyAliasMap::const_iterator alias_iter = aliases_.find(*iter);
if (alias_iter == aliases_.end())
continue;
// We found an alias.
const HostPortProxyPair& alias_pair = alias_iter->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::OnCertAdded(const X509Certificate* cert) {
CloseCurrentSessions(ERR_NETWORK_CHANGED);
}
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(ERR_NETWORK_CHANGED);
}
const HostPortProxyPair& SpdySessionPool::NormalizeListPair(
const HostPortProxyPair& host_port_proxy_pair) const {
if (!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;
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,
const BoundNetLog& net_log,
AddressList* addresses) const {
net::HostResolver::RequestInfo resolve_info(pair.first);
int rv = resolver_->ResolveFromCache(resolve_info, addresses, net_log);
DCHECK_NE(ERR_IO_PENDING, rv);
return rv == OK;
}
void SpdySessionPool::AddAlias(const IPEndPoint& endpoint,
const HostPortProxyPair& pair) {
DCHECK(enable_ip_pooling_);
aliases_[endpoint] = pair;
}
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, "Closing all sessions.");
}
}
void SpdySessionPool::CloseCurrentSessions(net::Error error) {
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(error, false, "Closing current sessions.");
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, "Closing idle sessions.");
}
}
}
} // namespace net