diff options
author | mbelshe@chromium.org <mbelshe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-03-25 05:33:02 +0000 |
---|---|---|
committer | mbelshe@chromium.org <mbelshe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-03-25 05:33:02 +0000 |
commit | 8b114dd781075604530544cf60e665fa4306903c (patch) | |
tree | caa2d8ef403d08ffab8dac31a126d84947990b1c /net/spdy | |
parent | bd0875ed222d58f51816ebb08e6203ef94053276 (diff) | |
download | chromium_src-8b114dd781075604530544cf60e665fa4306903c.zip chromium_src-8b114dd781075604530544cf60e665fa4306903c.tar.gz chromium_src-8b114dd781075604530544cf60e665fa4306903c.tar.bz2 |
Enable IP pooling for SPDY.
Added a command-line switch: --enable-ip-pooling
BUG=42669
TEST=SpdySessionTest.IPPool*
Review URL: http://codereview.chromium.org/6594116
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@79372 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/spdy')
-rw-r--r-- | net/spdy/spdy_session.cc | 12 | ||||
-rw-r--r-- | net/spdy/spdy_session.h | 7 | ||||
-rw-r--r-- | net/spdy/spdy_session_pool.cc | 214 | ||||
-rw-r--r-- | net/spdy/spdy_session_pool.h | 37 | ||||
-rw-r--r-- | net/spdy/spdy_session_unittest.cc | 103 | ||||
-rw-r--r-- | net/spdy/spdy_test_util.cc | 2 |
6 files changed, 349 insertions, 26 deletions
diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc index 9ab0155..048c837 100644 --- a/net/spdy/spdy_session.cc +++ b/net/spdy/spdy_session.cc @@ -302,6 +302,18 @@ net::Error SpdySession::InitializeWithSocket( return error; } +bool SpdySession::VerifyDomainAuthentication(const std::string& domain) { + if (state_ != CONNECTED) + return false; + + SSLInfo ssl_info; + bool was_npn_negotiated; + if (!GetSSLInfo(&ssl_info, &was_npn_negotiated)) + return true; // This is not a secure session, so all domains are okay. + + return ssl_info.cert->VerifyNameMatch(domain); +} + int SpdySession::GetPushStream( const GURL& url, scoped_refptr<SpdyStream>* stream, diff --git a/net/spdy/spdy_session.h b/net/spdy/spdy_session.h index 3afa7c0..b1abb13 100644 --- a/net/spdy/spdy_session.h +++ b/net/spdy/spdy_session.h @@ -97,6 +97,13 @@ class SpdySession : public base::RefCounted<SpdySession>, bool is_secure, int certificate_error_code); + // Check to see if this SPDY session can support an additional domain. + // If the session is un-authenticated, then this call always returns true. + // For SSL-based sessions, verifies that the certificate in use by this + // session provides authentication for the domain. + // NOTE: This function can have false negatives on some platforms. + bool VerifyDomainAuthentication(const std::string& domain); + // Send the SYN frame for |stream_id|. int WriteSynStream( spdy::SpdyStreamId stream_id, diff --git a/net/spdy/spdy_session_pool.cc b/net/spdy/spdy_session_pool.cc index 0220d11..de83271 100644 --- a/net/spdy/spdy_session_pool.cc +++ b/net/spdy/spdy_session_pool.cc @@ -5,20 +5,43 @@ #include "net/spdy/spdy_session_pool.h" #include "base/logging.h" +#include "base/metrics/histogram.h" #include "base/values.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; -int SpdySessionPool::g_max_sessions_per_domain = kMaxSessionsPerDomain; +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(SSLConfigService* ssl_config_service) - : ssl_config_service_(ssl_config_service) { +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); @@ -39,32 +62,46 @@ scoped_refptr<SpdySession> SpdySessionPool::Get( const BoundNetLog& net_log) { scoped_refptr<SpdySession> spdy_session; SpdySessionList* list = GetSessionList(host_port_proxy_pair); - if (list) { - if (list->size() >= static_cast<unsigned int>(g_max_sessions_per_domain)) { - spdy_session = list->front(); - list->pop_front(); + 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, + NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION_FROM_IP_POOL, make_scoped_refptr(new NetLogSourceParameter( - "session", spdy_session->net_log().source()))); + "session", spdy_session->net_log().source()))); + return spdy_session; } - } else { list = AddSessionList(host_port_proxy_pair); } DCHECK(list); - if (!spdy_session) { - spdy_session = new SpdySession(host_port_proxy_pair, this, &spdy_settings_, - net_log.net_log()); + 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_CREATED_NEW_SESSION, - make_scoped_refptr(new NetLogSourceParameter( - "session", spdy_session->net_log().source()))); + NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION, + make_scoped_refptr(new NetLogSourceParameter( + "session", spdy_session->net_log().source()))); + return spdy_session; } - DCHECK(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); - DCHECK_LE(list->size(), static_cast<unsigned int>(g_max_sessions_per_domain)); + 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; } @@ -75,6 +112,9 @@ net::Error SpdySessionPool::GetSpdySessionFromSocket( 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()); @@ -98,7 +138,11 @@ bool SpdySessionPool::HasSession( const HostPortProxyPair& host_port_proxy_pair) const { if (GetSessionList(host_port_proxy_pair)) return true; - return false; + + // 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) { @@ -138,6 +182,72 @@ 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(X509Certificate* cert) { CloseCurrentSessions(); } @@ -163,6 +273,16 @@ SpdySessionPool::SpdySessionList* 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; } @@ -171,9 +291,9 @@ SpdySessionPool::SpdySessionList* 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 NULL; - return it->second; + if (it != sessions_.end()) + return it->second; + return NULL; } void SpdySessionPool::RemoveSessionList( @@ -186,6 +306,56 @@ void SpdySessionPool::RemoveSessionList( } 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() { diff --git a/net/spdy/spdy_session_pool.h b/net/spdy/spdy_session_pool.h index caaaa67..eb127b5 100644 --- a/net/spdy/spdy_session_pool.h +++ b/net/spdy/spdy_session_pool.h @@ -16,6 +16,7 @@ #include "base/scoped_ptr.h" #include "net/base/cert_database.h" #include "net/base/host_port_pair.h" +#include "net/base/ip_endpoint.h" #include "net/base/net_errors.h" #include "net/base/network_change_notifier.h" #include "net/base/ssl_config_service.h" @@ -25,19 +26,21 @@ namespace net { +class AddressList; class BoundNetLog; class ClientSocketHandle; +class HostResolver; class HttpNetworkSession; class SpdySession; // This is a very simple pool for open SpdySessions. -// TODO(mbelshe): Make this production ready. class SpdySessionPool : public NetworkChangeNotifier::IPAddressObserver, public SSLConfigService::Observer, public CertDatabase::Observer { public: - explicit SpdySessionPool(SSLConfigService* ssl_config_service); + explicit SpdySessionPool(HostResolver* host_resolver, + SSLConfigService* ssl_config_service); virtual ~SpdySessionPool(); // Either returns an existing SpdySession or creates a new SpdySession for @@ -107,6 +110,10 @@ class SpdySessionPool // A debugging mode where we compress all accesses through a single domain. static void ForceSingleDomain() { g_force_single_domain = true; } + // Controls whether the pool allows use of a common session for domains + // which share IP address resolutions. + static void enable_ip_pooling(bool value) { g_enable_ip_pooling = value; } + // CertDatabase::Observer methods: virtual void OnUserCertAdded(X509Certificate* cert); @@ -117,6 +124,15 @@ class SpdySessionPool typedef std::list<scoped_refptr<SpdySession> > SpdySessionList; typedef std::map<HostPortProxyPair, SpdySessionList*> SpdySessionsMap; + typedef std::map<IPEndPoint, HostPortProxyPair> SpdyAliasMap; + + scoped_refptr<SpdySession> GetExistingSession( + SpdySessionList* list, + const BoundNetLog& net_log) const; + scoped_refptr<SpdySession> GetFromAlias( + const HostPortProxyPair& host_port_proxy_pair, + const BoundNetLog& net_log, + bool record_histograms) const; // Helper functions for manipulating the lists. const HostPortProxyPair& NormalizeListPair( @@ -127,15 +143,30 @@ class SpdySessionPool const HostPortProxyPair& host_port_proxy_pair) const; void RemoveSessionList(const HostPortProxyPair& host_port_proxy_pair); + // Does a DNS cache lookup for |pair|, and returns the |addresses| found. + // Returns true if addresses found, false otherwise. + bool LookupAddresses(const HostPortProxyPair& pair, + AddressList* addresses) const; + + // Add a set of |addresses| as IP-equivalent addresses for |pair|. + void AddAliases(const AddressList& addresses, const HostPortProxyPair& pair); + + // Remove all aliases for |pair| from the aliases table. + void RemoveAliases(const HostPortProxyPair& pair); + SpdySettingsStorage spdy_settings_; // This is our weak session pool - one session per domain. SpdySessionsMap sessions_; + // A map of IPEndPoint aliases for sessions. + SpdyAliasMap aliases_; - static int g_max_sessions_per_domain; + static size_t g_max_sessions_per_domain; static bool g_force_single_domain; + static bool g_enable_ip_pooling; const scoped_refptr<SSLConfigService> ssl_config_service_; + HostResolver* resolver_; DISALLOW_COPY_AND_ASSIGN(SpdySessionPool); }; diff --git a/net/spdy/spdy_session_unittest.cc b/net/spdy/spdy_session_unittest.cc index ee59f8b..473ec60 100644 --- a/net/spdy/spdy_session_unittest.cc +++ b/net/spdy/spdy_session_unittest.cc @@ -386,6 +386,109 @@ TEST_F(SpdySessionTest, SendSettingsOnNewSession) { EXPECT_TRUE(data.at_write_eof()); } +TEST_F(SpdySessionTest, IPPooling) { + const int kTestPort = 80; + struct TestHosts { + std::string name; + std::string iplist; + HostPortProxyPair pair; + } test_hosts[] = { + { "www.foo.com", "192.168.0.1,192.168.0.5" }, + { "images.foo.com", "192.168.0.2,192.168.0.3,192.168.0.5" }, + { "js.foo.com", "192.168.0.4,192.168.0.3" }, + }; + + SpdySessionDependencies session_deps; + session_deps.host_resolver->set_synchronous_mode(true); + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_hosts); i++) { + session_deps.host_resolver->rules()->AddIPLiteralRule(test_hosts[i].name, + test_hosts[i].iplist, ""); + + // This test requires that the HostResolver cache be populated. Normal + // code would have done this already, but we do it manually. + HostResolver::RequestInfo info(HostPortPair(test_hosts[i].name, kTestPort)); + AddressList result; + session_deps.host_resolver->Resolve( + info, &result, NULL, NULL, BoundNetLog()); + + // Setup a HostPortProxyPair + test_hosts[i].pair = HostPortProxyPair( + HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct()); + } + + MockConnect connect_data(false, OK); + MockRead reads[] = { + MockRead(false, ERR_IO_PENDING) // Stall forever. + }; + + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(false, OK); + session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); + + scoped_refptr<HttpNetworkSession> http_session( + SpdySessionDependencies::SpdyCreateSession(&session_deps)); + + // Setup the first session to the first host. + SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); + EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[0].pair)); + scoped_refptr<SpdySession> session = + spdy_session_pool->Get(test_hosts[0].pair, BoundNetLog()); + EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[0].pair)); + + HostPortPair test_host_port_pair(test_hosts[0].name, kTestPort); + scoped_refptr<TCPSocketParams> tcp_params( + new TCPSocketParams(test_host_port_pair, + MEDIUM, + GURL(), + false)); + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, + connection->Init(test_host_port_pair.ToString(), tcp_params, MEDIUM, + NULL, http_session->tcp_socket_pool(), + BoundNetLog())); + EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); + + // Flush the SpdySession::OnReadComplete() task. + MessageLoop::current()->RunAllPending(); + + // The third host has no overlap with the first, so it can't pool IPs. + EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair)); + + // The second host overlaps with the first, and should IP pool. + EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair)); + + // Verify that the second host, through a proxy, won't share the IP. + HostPortProxyPair proxy_pair(test_hosts[1].pair.first, + ProxyServer::FromPacString("HTTP http://proxy.foo.com/")); + EXPECT_FALSE(spdy_session_pool->HasSession(proxy_pair)); + + // Overlap between 2 and 3 does is not transitive to 1. + EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair)); + + // Create a new session to host 2. + scoped_refptr<SpdySession> session2 = + spdy_session_pool->Get(test_hosts[2].pair, BoundNetLog()); + + // Verify that we have sessions for everything. + EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[0].pair)); + EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair)); + EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[2].pair)); + + // Cleanup the sessions. + spdy_session_pool->Remove(session); + session = NULL; + spdy_session_pool->Remove(session2); + session2 = NULL; + + // Verify that the map is all cleaned up. + EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[0].pair)); + EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[1].pair)); + EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair)); +} + } // namespace } // namespace net diff --git a/net/spdy/spdy_test_util.cc b/net/spdy/spdy_test_util.cc index 7e1711a..1adcb24 100644 --- a/net/spdy/spdy_test_util.cc +++ b/net/spdy/spdy_test_util.cc @@ -884,7 +884,7 @@ int CombineFrames(const spdy::SpdyFrame** frames, int num_frames, } SpdySessionDependencies::SpdySessionDependencies() - : host_resolver(new MockHostResolver), + : host_resolver(new MockCachingHostResolver), cert_verifier(new CertVerifier), proxy_service(ProxyService::CreateDirect()), ssl_config_service(new SSLConfigServiceDefaults), |