spdy_session_pool.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1// Copyright (c) 2012 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/callback.h"
8#include "base/logging.h"
9#include "base/metrics/histogram.h"
10#include "base/values.h"
11#include "net/base/address_list.h"
12#include "net/http/http_network_session.h"
13#include "net/http/http_server_properties.h"
14#include "net/spdy/spdy_session.h"
15
16
17namespace net {
18
19namespace {
20
21enum SpdySessionGetTypes {
22  CREATED_NEW                 = 0,
23  FOUND_EXISTING              = 1,
24  FOUND_EXISTING_FROM_IP_POOL = 2,
25  IMPORTED_FROM_SOCKET        = 3,
26  SPDY_SESSION_GET_MAX        = 4
27};
28
29bool HostPortProxyPairsAreEqual(const HostPortProxyPair& a,
30                                const HostPortProxyPair& b) {
31  return a.first.Equals(b.first) && a.second == b.second;
32}
33
34}
35
36// The maximum number of sessions to open to a single domain.
37static const size_t kMaxSessionsPerDomain = 1;
38
39SpdySessionPool::SpdySessionPool(
40    HostResolver* resolver,
41    SSLConfigService* ssl_config_service,
42    HttpServerProperties* http_server_properties,
43    size_t max_sessions_per_domain,
44    bool force_single_domain,
45    bool enable_ip_pooling,
46    bool enable_credential_frames,
47    bool enable_compression,
48    bool enable_ping_based_connection_checking,
49    NextProto default_protocol,
50    size_t stream_initial_recv_window_size,
51    size_t initial_max_concurrent_streams,
52    size_t max_concurrent_streams_limit,
53    SpdySessionPool::TimeFunc time_func,
54    const std::string& trusted_spdy_proxy)
55    : http_server_properties_(http_server_properties),
56      ssl_config_service_(ssl_config_service),
57      resolver_(resolver),
58      verify_domain_authentication_(true),
59      enable_sending_initial_settings_(true),
60      max_sessions_per_domain_(max_sessions_per_domain == 0 ?
61                               kMaxSessionsPerDomain :
62                               max_sessions_per_domain),
63      force_single_domain_(force_single_domain),
64      enable_ip_pooling_(enable_ip_pooling),
65      enable_credential_frames_(enable_credential_frames),
66      enable_compression_(enable_compression),
67      enable_ping_based_connection_checking_(
68          enable_ping_based_connection_checking),
69      default_protocol_(default_protocol),
70      stream_initial_recv_window_size_(stream_initial_recv_window_size),
71      initial_max_concurrent_streams_(initial_max_concurrent_streams),
72      max_concurrent_streams_limit_(max_concurrent_streams_limit),
73      time_func_(time_func),
74      trusted_spdy_proxy_(
75          HostPortPair::FromString(trusted_spdy_proxy)) {
76  NetworkChangeNotifier::AddIPAddressObserver(this);
77  if (ssl_config_service_)
78    ssl_config_service_->AddObserver(this);
79  CertDatabase::GetInstance()->AddObserver(this);
80}
81
82SpdySessionPool::~SpdySessionPool() {
83  CloseAllSessions();
84
85  if (ssl_config_service_)
86    ssl_config_service_->RemoveObserver(this);
87  NetworkChangeNotifier::RemoveIPAddressObserver(this);
88  CertDatabase::GetInstance()->RemoveObserver(this);
89}
90
91scoped_refptr<SpdySession> SpdySessionPool::Get(
92    const HostPortProxyPair& host_port_proxy_pair,
93    const BoundNetLog& net_log) {
94  return GetInternal(host_port_proxy_pair, net_log, false);
95}
96
97scoped_refptr<SpdySession> SpdySessionPool::GetIfExists(
98    const HostPortProxyPair& host_port_proxy_pair,
99    const BoundNetLog& net_log) {
100  return GetInternal(host_port_proxy_pair, net_log, true);
101}
102
103scoped_refptr<SpdySession> SpdySessionPool::GetInternal(
104    const HostPortProxyPair& host_port_proxy_pair,
105    const BoundNetLog& net_log,
106    bool only_use_existing_sessions) {
107  scoped_refptr<SpdySession> spdy_session;
108  SpdySessionList* list = GetSessionList(host_port_proxy_pair);
109  if (!list) {
110    // Check if we have a Session through a domain alias.
111    spdy_session = GetFromAlias(host_port_proxy_pair, net_log, true);
112    if (spdy_session) {
113      UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
114                                FOUND_EXISTING_FROM_IP_POOL,
115                                SPDY_SESSION_GET_MAX);
116      net_log.AddEvent(
117          NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION_FROM_IP_POOL,
118          spdy_session->net_log().source().ToEventParametersCallback());
119      // Add this session to the map so that we can find it next time.
120      list = AddSessionList(host_port_proxy_pair);
121      list->push_back(spdy_session);
122      spdy_session->AddPooledAlias(host_port_proxy_pair);
123      return spdy_session;
124    } else if (only_use_existing_sessions) {
125      return NULL;
126    }
127    list = AddSessionList(host_port_proxy_pair);
128  }
129
130  DCHECK(list);
131  if (list->size() && list->size() == max_sessions_per_domain_) {
132    UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
133                              FOUND_EXISTING,
134                              SPDY_SESSION_GET_MAX);
135    spdy_session = GetExistingSession(list, net_log);
136    net_log.AddEvent(
137      NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION,
138      spdy_session->net_log().source().ToEventParametersCallback());
139    return spdy_session;
140  }
141
142  DCHECK(!only_use_existing_sessions);
143
144  spdy_session = new SpdySession(host_port_proxy_pair, this,
145                                 http_server_properties_,
146                                 verify_domain_authentication_,
147                                 enable_sending_initial_settings_,
148                                 enable_credential_frames_,
149                                 enable_compression_,
150                                 enable_ping_based_connection_checking_,
151                                 default_protocol_,
152                                 stream_initial_recv_window_size_,
153                                 initial_max_concurrent_streams_,
154                                 max_concurrent_streams_limit_,
155                                 time_func_,
156                                 trusted_spdy_proxy_,
157                                 net_log.net_log());
158  UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
159                            CREATED_NEW,
160                            SPDY_SESSION_GET_MAX);
161  list->push_back(spdy_session);
162  net_log.AddEvent(
163      NetLog::TYPE_SPDY_SESSION_POOL_CREATED_NEW_SESSION,
164      spdy_session->net_log().source().ToEventParametersCallback());
165  DCHECK_LE(list->size(), max_sessions_per_domain_);
166  return spdy_session;
167}
168
169net::Error SpdySessionPool::GetSpdySessionFromSocket(
170    const HostPortProxyPair& host_port_proxy_pair,
171    ClientSocketHandle* connection,
172    const BoundNetLog& net_log,
173    int certificate_error_code,
174    scoped_refptr<SpdySession>* spdy_session,
175    bool is_secure) {
176  UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
177                            IMPORTED_FROM_SOCKET,
178                            SPDY_SESSION_GET_MAX);
179  // Create the SPDY session and add it to the pool.
180  *spdy_session = new SpdySession(host_port_proxy_pair, this,
181                                  http_server_properties_,
182                                  verify_domain_authentication_,
183                                  enable_sending_initial_settings_,
184                                  enable_credential_frames_,
185                                  enable_compression_,
186                                  enable_ping_based_connection_checking_,
187                                  default_protocol_,
188                                  stream_initial_recv_window_size_,
189                                  initial_max_concurrent_streams_,
190                                  max_concurrent_streams_limit_,
191                                  time_func_,
192                                  trusted_spdy_proxy_,
193                                  net_log.net_log());
194  SpdySessionList* list = GetSessionList(host_port_proxy_pair);
195  if (!list)
196    list = AddSessionList(host_port_proxy_pair);
197  DCHECK(list->empty());
198  list->push_back(*spdy_session);
199
200  net_log.AddEvent(
201      NetLog::TYPE_SPDY_SESSION_POOL_IMPORTED_SESSION_FROM_SOCKET,
202      (*spdy_session)->net_log().source().ToEventParametersCallback());
203
204  // We have a new session.  Lookup the IP address for this session so that we
205  // can match future Sessions (potentially to different domains) which can
206  // potentially be pooled with this one. Because GetPeerAddress() reports the
207  // proxy's address instead of the origin server, check to see if this is a
208  // direct connection.
209  if (enable_ip_pooling_  && host_port_proxy_pair.second.is_direct()) {
210    IPEndPoint address;
211    if (connection->socket()->GetPeerAddress(&address) == OK)
212      AddAlias(address, host_port_proxy_pair);
213  }
214
215  // Now we can initialize the session with the SSL socket.
216  return (*spdy_session)->InitializeWithSocket(connection, is_secure,
217                                               certificate_error_code);
218}
219
220bool SpdySessionPool::HasSession(
221    const HostPortProxyPair& host_port_proxy_pair) const {
222  if (GetSessionList(host_port_proxy_pair))
223    return true;
224
225  // Check if we have a session via an alias.
226  scoped_refptr<SpdySession> spdy_session =
227      GetFromAlias(host_port_proxy_pair, BoundNetLog(), false);
228  return spdy_session.get() != NULL;
229}
230
231void SpdySessionPool::Remove(const scoped_refptr<SpdySession>& session) {
232  bool ok = RemoveFromSessionList(session, session->host_port_proxy_pair());
233  DCHECK(ok);
234  session->net_log().AddEvent(
235      NetLog::TYPE_SPDY_SESSION_POOL_REMOVE_SESSION,
236      session->net_log().source().ToEventParametersCallback());
237
238  const std::set<HostPortProxyPair>& aliases = session->pooled_aliases();
239  for (std::set<HostPortProxyPair>::const_iterator it = aliases.begin();
240       it != aliases.end(); ++it) {
241    ok = RemoveFromSessionList(session, *it);
242    DCHECK(ok);
243  }
244}
245
246bool SpdySessionPool::RemoveFromSessionList(
247    const scoped_refptr<SpdySession>& session,
248    const HostPortProxyPair& pair) {
249  SpdySessionList* list = GetSessionList(pair);
250  if (!list)
251    return false;
252  list->remove(session);
253  if (list->empty())
254    RemoveSessionList(pair);
255  return true;
256}
257
258Value* SpdySessionPool::SpdySessionPoolInfoToValue() const {
259  ListValue* list = new ListValue();
260
261  for (SpdySessionsMap::const_iterator it = sessions_.begin();
262       it != sessions_.end(); ++it) {
263    SpdySessionList* sessions = it->second;
264    for (SpdySessionList::const_iterator session = sessions->begin();
265         session != sessions->end(); ++session) {
266      // Only add the session if the key in the map matches the main
267      // host_port_proxy_pair (not an alias).
268      const HostPortProxyPair& key = it->first;
269      const HostPortProxyPair& pair = session->get()->host_port_proxy_pair();
270      if (key.first.Equals(pair.first) && key.second == pair.second)
271        list->Append(session->get()->GetInfoAsValue());
272    }
273  }
274  return list;
275}
276
277void SpdySessionPool::OnIPAddressChanged() {
278  CloseCurrentSessions(ERR_NETWORK_CHANGED);
279  http_server_properties_->ClearAllSpdySettings();
280}
281
282void SpdySessionPool::OnSSLConfigChanged() {
283  CloseCurrentSessions(ERR_NETWORK_CHANGED);
284}
285
286scoped_refptr<SpdySession> SpdySessionPool::GetExistingSession(
287    SpdySessionList* list,
288    const BoundNetLog& net_log) const {
289  DCHECK(list);
290  DCHECK_LT(0u, list->size());
291  scoped_refptr<SpdySession> spdy_session = list->front();
292  if (list->size() > 1) {
293    list->pop_front();  // Rotate the list.
294    list->push_back(spdy_session);
295  }
296
297  return spdy_session;
298}
299
300scoped_refptr<SpdySession> SpdySessionPool::GetFromAlias(
301      const HostPortProxyPair& host_port_proxy_pair,
302      const BoundNetLog& net_log,
303      bool record_histograms) const {
304  // We should only be checking aliases when there is no direct session.
305  DCHECK(!GetSessionList(host_port_proxy_pair));
306
307  if (!enable_ip_pooling_)
308    return NULL;
309
310  AddressList addresses;
311  if (!LookupAddresses(host_port_proxy_pair, net_log, &addresses))
312    return NULL;
313  for (AddressList::const_iterator iter = addresses.begin();
314       iter != addresses.end();
315       ++iter) {
316    SpdyAliasMap::const_iterator alias_iter = aliases_.find(*iter);
317    if (alias_iter == aliases_.end())
318      continue;
319
320    // We found an alias.
321    const HostPortProxyPair& alias_pair = alias_iter->second;
322
323    // If the proxy settings match, we can reuse this session.
324    if (!(alias_pair.second == host_port_proxy_pair.second))
325      continue;
326
327    SpdySessionList* list = GetSessionList(alias_pair);
328    if (!list) {
329      NOTREACHED();  // It shouldn't be in the aliases table if we can't get it!
330      continue;
331    }
332
333    scoped_refptr<SpdySession> spdy_session = GetExistingSession(list, net_log);
334    // If the SPDY session is a secure one, we need to verify that the server
335    // is authenticated to serve traffic for |host_port_proxy_pair| too.
336    if (!spdy_session->VerifyDomainAuthentication(
337            host_port_proxy_pair.first.host())) {
338      if (record_histograms)
339        UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 0, 2);
340      continue;
341    }
342    if (record_histograms)
343      UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 1, 2);
344    return spdy_session;
345  }
346  return NULL;
347}
348
349void SpdySessionPool::OnCertAdded(const X509Certificate* cert) {
350  CloseCurrentSessions(ERR_NETWORK_CHANGED);
351}
352
353void SpdySessionPool::OnCertTrustChanged(const X509Certificate* cert) {
354  // Per wtc, we actually only need to CloseCurrentSessions when trust is
355  // reduced. CloseCurrentSessions now because OnCertTrustChanged does not
356  // tell us this.
357  // See comments in ClientSocketPoolManager::OnCertTrustChanged.
358  CloseCurrentSessions(ERR_NETWORK_CHANGED);
359}
360
361const HostPortProxyPair& SpdySessionPool::NormalizeListPair(
362    const HostPortProxyPair& host_port_proxy_pair) const {
363  if (!force_single_domain_)
364    return host_port_proxy_pair;
365
366  static HostPortProxyPair* single_domain_pair = NULL;
367  if (!single_domain_pair) {
368    HostPortPair single_domain = HostPortPair("singledomain.com", 80);
369    single_domain_pair = new HostPortProxyPair(single_domain,
370                                               ProxyServer::Direct());
371  }
372  return *single_domain_pair;
373}
374
375SpdySessionPool::SpdySessionList*
376    SpdySessionPool::AddSessionList(
377        const HostPortProxyPair& host_port_proxy_pair) {
378  const HostPortProxyPair& pair = NormalizeListPair(host_port_proxy_pair);
379  DCHECK(sessions_.find(pair) == sessions_.end());
380  SpdySessionPool::SpdySessionList* list = new SpdySessionList();
381  sessions_[pair] = list;
382  return list;
383}
384
385SpdySessionPool::SpdySessionList*
386    SpdySessionPool::GetSessionList(
387        const HostPortProxyPair& host_port_proxy_pair) const {
388  const HostPortProxyPair& pair = NormalizeListPair(host_port_proxy_pair);
389  SpdySessionsMap::const_iterator it = sessions_.find(pair);
390  if (it != sessions_.end())
391    return it->second;
392  return NULL;
393}
394
395void SpdySessionPool::RemoveSessionList(
396    const HostPortProxyPair& host_port_proxy_pair) {
397  const HostPortProxyPair& pair = NormalizeListPair(host_port_proxy_pair);
398  SpdySessionList* list = GetSessionList(pair);
399  if (list) {
400    delete list;
401    sessions_.erase(pair);
402  } else {
403    DCHECK(false) << "removing orphaned session list";
404  }
405  RemoveAliases(host_port_proxy_pair);
406}
407
408bool SpdySessionPool::LookupAddresses(const HostPortProxyPair& pair,
409                                      const BoundNetLog& net_log,
410                                      AddressList* addresses) const {
411  net::HostResolver::RequestInfo resolve_info(pair.first);
412  int rv = resolver_->ResolveFromCache(resolve_info, addresses, net_log);
413  DCHECK_NE(ERR_IO_PENDING, rv);
414  return rv == OK;
415}
416
417void SpdySessionPool::AddAlias(const IPEndPoint& endpoint,
418                               const HostPortProxyPair& pair) {
419  DCHECK(enable_ip_pooling_);
420  aliases_[endpoint] = pair;
421}
422
423void SpdySessionPool::RemoveAliases(const HostPortProxyPair& pair) {
424  // Walk the aliases map, find references to this pair.
425  // TODO(mbelshe):  Figure out if this is too expensive.
426  SpdyAliasMap::iterator alias_it = aliases_.begin();
427  while (alias_it != aliases_.end()) {
428    if (HostPortProxyPairsAreEqual(alias_it->second, pair)) {
429      aliases_.erase(alias_it);
430      alias_it = aliases_.begin();  // Iterator was invalidated.
431      continue;
432    }
433    ++alias_it;
434  }
435}
436
437void SpdySessionPool::CloseAllSessions() {
438  while (!sessions_.empty()) {
439    SpdySessionList* list = sessions_.begin()->second;
440    CHECK(list);
441    const scoped_refptr<SpdySession>& session = list->front();
442    CHECK(session);
443    // This call takes care of removing the session from the pool, as well as
444    // removing the session list if the list is empty.
445    session->CloseSessionOnError(
446        net::ERR_ABORTED, true, "Closing all sessions.");
447  }
448}
449
450void SpdySessionPool::CloseCurrentSessions(net::Error error) {
451  SpdySessionsMap old_map;
452  old_map.swap(sessions_);
453  for (SpdySessionsMap::const_iterator it = old_map.begin();
454       it != old_map.end(); ++it) {
455    SpdySessionList* list = it->second;
456    CHECK(list);
457    const scoped_refptr<SpdySession>& session = list->front();
458    CHECK(session);
459    session->set_spdy_session_pool(NULL);
460  }
461
462  while (!old_map.empty()) {
463    SpdySessionList* list = old_map.begin()->second;
464    CHECK(list);
465    const scoped_refptr<SpdySession>& session = list->front();
466    CHECK(session);
467    session->CloseSessionOnError(error, false, "Closing current sessions.");
468    list->pop_front();
469    if (list->empty()) {
470      delete list;
471      RemoveAliases(old_map.begin()->first);
472      old_map.erase(old_map.begin()->first);
473    }
474  }
475  DCHECK(sessions_.empty());
476  DCHECK(aliases_.empty());
477}
478
479void SpdySessionPool::CloseIdleSessions() {
480  SpdySessionsMap::const_iterator map_it = sessions_.begin();
481  while (map_it != sessions_.end()) {
482    SpdySessionList* list = map_it->second;
483    CHECK(list);
484
485    // Assumes there is only 1 element in the list.
486    SpdySessionList::iterator session_it = list->begin();
487    const scoped_refptr<SpdySession>& session = *session_it;
488    CHECK(session);
489    if (session->is_active()) {
490      ++map_it;
491      continue;
492    }
493
494    HostPortProxyPair key = map_it->first;
495    session->CloseSessionOnError(
496        net::ERR_ABORTED, true, "Closing idle sessions.");
497    // CloseSessionOnError can invalidate the iterator.
498    map_it = sessions_.lower_bound(key);
499  }
500}
501
502}  // namespace net
503