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
16namespace net {
17
18namespace {
19
20enum 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
28bool 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.
36static const size_t kMaxSessionsPerDomain = 1;
37
38size_t SpdySessionPool::g_max_sessions_per_domain = kMaxSessionsPerDomain;
39bool SpdySessionPool::g_force_single_domain = false;
40bool SpdySessionPool::g_enable_ip_pooling = true;
41
42SpdySessionPool::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
52SpdySessionPool::~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
61scoped_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
109net::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
138bool 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
149void 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
163Value* 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
178void SpdySessionPool::OnIPAddressChanged() {
179  CloseCurrentSessions();
180}
181
182void SpdySessionPool::OnSSLConfigChanged() {
183  CloseCurrentSessions();
184}
185
186scoped_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
200scoped_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
252void SpdySessionPool::OnUserCertAdded(const X509Certificate* cert) {
253  CloseCurrentSessions();
254}
255
256void 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
264const 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
278SpdySessionPool::SpdySessionList*
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
298SpdySessionPool::SpdySessionList*
299    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
308void 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
321bool 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
334void 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
356void 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
370void 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
382void 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
411void 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