• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
HostPortProxyPairsAreEqual(const HostPortProxyPair & a,const HostPortProxyPair & b)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 
SpdySessionPool(HostResolver * resolver,SSLConfigService * ssl_config_service)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 
~SpdySessionPool()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 
Get(const HostPortProxyPair & host_port_proxy_pair,const BoundNetLog & net_log)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 
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)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 
HasSession(const HostPortProxyPair & host_port_proxy_pair) const138 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 
Remove(const scoped_refptr<SpdySession> & session)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 
SpdySessionPoolInfoToValue() const163 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 
OnIPAddressChanged()178 void SpdySessionPool::OnIPAddressChanged() {
179   CloseCurrentSessions();
180 }
181 
OnSSLConfigChanged()182 void SpdySessionPool::OnSSLConfigChanged() {
183   CloseCurrentSessions();
184 }
185 
GetExistingSession(SpdySessionList * list,const BoundNetLog & net_log) const186 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 
GetFromAlias(const HostPortProxyPair & host_port_proxy_pair,const BoundNetLog & net_log,bool record_histograms) const200 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 
OnUserCertAdded(const X509Certificate * cert)252 void SpdySessionPool::OnUserCertAdded(const X509Certificate* cert) {
253   CloseCurrentSessions();
254 }
255 
OnCertTrustChanged(const X509Certificate * cert)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 
NormalizeListPair(const HostPortProxyPair & host_port_proxy_pair) const264 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*
AddSessionList(const HostPortProxyPair & host_port_proxy_pair)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*
GetSessionList(const HostPortProxyPair & host_port_proxy_pair) const299     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 
RemoveSessionList(const HostPortProxyPair & host_port_proxy_pair)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 
LookupAddresses(const HostPortProxyPair & pair,AddressList * addresses) const321 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 
AddAliases(const AddressList & addresses,const HostPortProxyPair & pair)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 
RemoveAliases(const HostPortProxyPair & pair)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 
CloseAllSessions()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 
CloseCurrentSessions()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 
CloseIdleSessions()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