Home | History | Annotate | Download | only in spdy
      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 
     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 
     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 
     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 
     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 
    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 
    138 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 
    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 
    163 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 
    178 void SpdySessionPool::OnIPAddressChanged() {
    179   CloseCurrentSessions();
    180 }
    181 
    182 void SpdySessionPool::OnSSLConfigChanged() {
    183   CloseCurrentSessions();
    184 }
    185 
    186 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 
    200 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 
    252 void SpdySessionPool::OnUserCertAdded(const X509Certificate* cert) {
    253   CloseCurrentSessions();
    254 }
    255 
    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 
    264 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*
    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*
    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 
    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 
    321 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 
    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 
    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 
    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 
    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 
    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