Home | History | Annotate | Download | only in spdy
      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/logging.h"
      8 #include "base/metrics/histogram.h"
      9 #include "base/values.h"
     10 #include "net/base/address_list.h"
     11 #include "net/http/http_network_session.h"
     12 #include "net/http/http_server_properties.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 }  // namespace
     29 
     30 SpdySessionPool::SpdySessionPool(
     31     HostResolver* resolver,
     32     SSLConfigService* ssl_config_service,
     33     const base::WeakPtr<HttpServerProperties>& http_server_properties,
     34     bool force_single_domain,
     35     bool enable_compression,
     36     bool enable_ping_based_connection_checking,
     37     NextProto default_protocol,
     38     size_t stream_initial_recv_window_size,
     39     size_t initial_max_concurrent_streams,
     40     size_t max_concurrent_streams_limit,
     41     SpdySessionPool::TimeFunc time_func,
     42     const std::string& trusted_spdy_proxy)
     43     : http_server_properties_(http_server_properties),
     44       ssl_config_service_(ssl_config_service),
     45       resolver_(resolver),
     46       verify_domain_authentication_(true),
     47       enable_sending_initial_data_(true),
     48       force_single_domain_(force_single_domain),
     49       enable_compression_(enable_compression),
     50       enable_ping_based_connection_checking_(
     51           enable_ping_based_connection_checking),
     52       // TODO(akalin): Force callers to have a valid value of
     53       // |default_protocol_|.
     54       default_protocol_(
     55           (default_protocol == kProtoUnknown) ?
     56           kProtoSPDY3 : default_protocol),
     57       stream_initial_recv_window_size_(stream_initial_recv_window_size),
     58       initial_max_concurrent_streams_(initial_max_concurrent_streams),
     59       max_concurrent_streams_limit_(max_concurrent_streams_limit),
     60       time_func_(time_func),
     61       trusted_spdy_proxy_(
     62           HostPortPair::FromString(trusted_spdy_proxy)) {
     63   DCHECK(default_protocol_ >= kProtoSPDYMinimumVersion &&
     64          default_protocol_ <= kProtoSPDYMaximumVersion);
     65   NetworkChangeNotifier::AddIPAddressObserver(this);
     66   if (ssl_config_service_.get())
     67     ssl_config_service_->AddObserver(this);
     68   CertDatabase::GetInstance()->AddObserver(this);
     69 }
     70 
     71 SpdySessionPool::~SpdySessionPool() {
     72   CloseAllSessions();
     73 
     74   while (!sessions_.empty()) {
     75     // Destroy sessions to enforce that lifetime is scoped to SpdySessionPool.
     76     // Write callbacks queued upon session drain are not invoked.
     77     RemoveUnavailableSession((*sessions_.begin())->GetWeakPtr());
     78   }
     79 
     80   if (ssl_config_service_.get())
     81     ssl_config_service_->RemoveObserver(this);
     82   NetworkChangeNotifier::RemoveIPAddressObserver(this);
     83   CertDatabase::GetInstance()->RemoveObserver(this);
     84 }
     85 
     86 base::WeakPtr<SpdySession> SpdySessionPool::CreateAvailableSessionFromSocket(
     87     const SpdySessionKey& key,
     88     scoped_ptr<ClientSocketHandle> connection,
     89     const BoundNetLog& net_log,
     90     int certificate_error_code,
     91     bool is_secure) {
     92   DCHECK_GE(default_protocol_, kProtoSPDYMinimumVersion);
     93   DCHECK_LE(default_protocol_, kProtoSPDYMaximumVersion);
     94 
     95   UMA_HISTOGRAM_ENUMERATION(
     96       "Net.SpdySessionGet", IMPORTED_FROM_SOCKET, SPDY_SESSION_GET_MAX);
     97 
     98   scoped_ptr<SpdySession> new_session(
     99       new SpdySession(key,
    100                       http_server_properties_,
    101                       verify_domain_authentication_,
    102                       enable_sending_initial_data_,
    103                       enable_compression_,
    104                       enable_ping_based_connection_checking_,
    105                       default_protocol_,
    106                       stream_initial_recv_window_size_,
    107                       initial_max_concurrent_streams_,
    108                       max_concurrent_streams_limit_,
    109                       time_func_,
    110                       trusted_spdy_proxy_,
    111                       net_log.net_log()));
    112 
    113   new_session->InitializeWithSocket(
    114       connection.Pass(), this, is_secure, certificate_error_code);
    115 
    116   base::WeakPtr<SpdySession> available_session = new_session->GetWeakPtr();
    117   sessions_.insert(new_session.release());
    118   MapKeyToAvailableSession(key, available_session);
    119 
    120   net_log.AddEvent(
    121       NetLog::TYPE_SPDY_SESSION_POOL_IMPORTED_SESSION_FROM_SOCKET,
    122       available_session->net_log().source().ToEventParametersCallback());
    123 
    124   // Look up the IP address for this session so that we can match
    125   // future sessions (potentially to different domains) which can
    126   // potentially be pooled with this one. Because GetPeerAddress()
    127   // reports the proxy's address instead of the origin server, check
    128   // to see if this is a direct connection.
    129   if (key.proxy_server().is_direct()) {
    130     IPEndPoint address;
    131     if (available_session->GetPeerAddress(&address) == OK)
    132       aliases_[address] = key;
    133   }
    134 
    135   return available_session;
    136 }
    137 
    138 base::WeakPtr<SpdySession> SpdySessionPool::FindAvailableSession(
    139     const SpdySessionKey& key,
    140     const BoundNetLog& net_log) {
    141   AvailableSessionMap::iterator it = LookupAvailableSessionByKey(key);
    142   if (it != available_sessions_.end()) {
    143     UMA_HISTOGRAM_ENUMERATION(
    144         "Net.SpdySessionGet", FOUND_EXISTING, SPDY_SESSION_GET_MAX);
    145     net_log.AddEvent(
    146         NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION,
    147         it->second->net_log().source().ToEventParametersCallback());
    148     return it->second;
    149   }
    150 
    151   // Look up the key's from the resolver's cache.
    152   net::HostResolver::RequestInfo resolve_info(key.host_port_pair());
    153   AddressList addresses;
    154   int rv = resolver_->ResolveFromCache(resolve_info, &addresses, net_log);
    155   DCHECK_NE(rv, ERR_IO_PENDING);
    156   if (rv != OK)
    157     return base::WeakPtr<SpdySession>();
    158 
    159   // Check if we have a session through a domain alias.
    160   for (AddressList::const_iterator address_it = addresses.begin();
    161        address_it != addresses.end();
    162        ++address_it) {
    163     AliasMap::const_iterator alias_it = aliases_.find(*address_it);
    164     if (alias_it == aliases_.end())
    165       continue;
    166 
    167     // We found an alias.
    168     const SpdySessionKey& alias_key = alias_it->second;
    169 
    170     // We can reuse this session only if the proxy and privacy
    171     // settings match.
    172     if (!(alias_key.proxy_server() == key.proxy_server()) ||
    173         !(alias_key.privacy_mode() == key.privacy_mode()))
    174       continue;
    175 
    176     AvailableSessionMap::iterator available_session_it =
    177         LookupAvailableSessionByKey(alias_key);
    178     if (available_session_it == available_sessions_.end()) {
    179       NOTREACHED();  // It shouldn't be in the aliases table if we can't get it!
    180       continue;
    181     }
    182 
    183     const base::WeakPtr<SpdySession>& available_session =
    184         available_session_it->second;
    185     DCHECK(ContainsKey(sessions_, available_session.get()));
    186     // If the session is a secure one, we need to verify that the
    187     // server is authenticated to serve traffic for |host_port_proxy_pair| too.
    188     if (!available_session->VerifyDomainAuthentication(
    189             key.host_port_pair().host())) {
    190       UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 0, 2);
    191       continue;
    192     }
    193 
    194     UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 1, 2);
    195     UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
    196                               FOUND_EXISTING_FROM_IP_POOL,
    197                               SPDY_SESSION_GET_MAX);
    198     net_log.AddEvent(
    199         NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION_FROM_IP_POOL,
    200         available_session->net_log().source().ToEventParametersCallback());
    201     // Add this session to the map so that we can find it next time.
    202     MapKeyToAvailableSession(key, available_session);
    203     available_session->AddPooledAlias(key);
    204     return available_session;
    205   }
    206 
    207   return base::WeakPtr<SpdySession>();
    208 }
    209 
    210 void SpdySessionPool::MakeSessionUnavailable(
    211     const base::WeakPtr<SpdySession>& available_session) {
    212   UnmapKey(available_session->spdy_session_key());
    213   RemoveAliases(available_session->spdy_session_key());
    214   const std::set<SpdySessionKey>& aliases = available_session->pooled_aliases();
    215   for (std::set<SpdySessionKey>::const_iterator it = aliases.begin();
    216        it != aliases.end(); ++it) {
    217     UnmapKey(*it);
    218     RemoveAliases(*it);
    219   }
    220   DCHECK(!IsSessionAvailable(available_session));
    221 }
    222 
    223 void SpdySessionPool::RemoveUnavailableSession(
    224     const base::WeakPtr<SpdySession>& unavailable_session) {
    225   DCHECK(!IsSessionAvailable(unavailable_session));
    226 
    227   unavailable_session->net_log().AddEvent(
    228       NetLog::TYPE_SPDY_SESSION_POOL_REMOVE_SESSION,
    229       unavailable_session->net_log().source().ToEventParametersCallback());
    230 
    231   SessionSet::iterator it = sessions_.find(unavailable_session.get());
    232   CHECK(it != sessions_.end());
    233   scoped_ptr<SpdySession> owned_session(*it);
    234   sessions_.erase(it);
    235 }
    236 
    237 // Make a copy of |sessions_| in the Close* functions below to avoid
    238 // reentrancy problems. Since arbitrary functions get called by close
    239 // handlers, it doesn't suffice to simply increment the iterator
    240 // before closing.
    241 
    242 void SpdySessionPool::CloseCurrentSessions(net::Error error) {
    243   CloseCurrentSessionsHelper(error, "Closing current sessions.",
    244                              false /* idle_only */);
    245 }
    246 
    247 void SpdySessionPool::CloseCurrentIdleSessions() {
    248   CloseCurrentSessionsHelper(ERR_ABORTED, "Closing idle sessions.",
    249                              true /* idle_only */);
    250 }
    251 
    252 void SpdySessionPool::CloseAllSessions() {
    253   while (!available_sessions_.empty()) {
    254     CloseCurrentSessionsHelper(ERR_ABORTED, "Closing all sessions.",
    255                                false /* idle_only */);
    256   }
    257 }
    258 
    259 base::Value* SpdySessionPool::SpdySessionPoolInfoToValue() const {
    260   base::ListValue* list = new base::ListValue();
    261 
    262   for (AvailableSessionMap::const_iterator it = available_sessions_.begin();
    263        it != available_sessions_.end(); ++it) {
    264     // Only add the session if the key in the map matches the main
    265     // host_port_proxy_pair (not an alias).
    266     const SpdySessionKey& key = it->first;
    267     const SpdySessionKey& session_key = it->second->spdy_session_key();
    268     if (key.Equals(session_key))
    269       list->Append(it->second->GetInfoAsValue());
    270   }
    271   return list;
    272 }
    273 
    274 void SpdySessionPool::OnIPAddressChanged() {
    275   WeakSessionList current_sessions = GetCurrentSessions();
    276   for (WeakSessionList::const_iterator it = current_sessions.begin();
    277        it != current_sessions.end(); ++it) {
    278     if (!*it)
    279       continue;
    280 
    281 // For OSs that terminate TCP connections upon relevant network changes,
    282 // attempt to preserve active streams by marking all sessions as going
    283 // away, rather than explicitly closing them. Streams may still fail due
    284 // to a generated TCP reset.
    285 #if defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_IOS)
    286     (*it)->MakeUnavailable();
    287     (*it)->StartGoingAway(kLastStreamId, ERR_NETWORK_CHANGED);
    288     (*it)->MaybeFinishGoingAway();
    289 #else
    290     (*it)->CloseSessionOnError(ERR_NETWORK_CHANGED,
    291                                "Closing current sessions.");
    292     DCHECK((*it)->IsDraining());
    293 #endif  // defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_IOS)
    294     DCHECK(!IsSessionAvailable(*it));
    295   }
    296   http_server_properties_->ClearAllSpdySettings();
    297 }
    298 
    299 void SpdySessionPool::OnSSLConfigChanged() {
    300   CloseCurrentSessions(ERR_NETWORK_CHANGED);
    301 }
    302 
    303 void SpdySessionPool::OnCertAdded(const X509Certificate* cert) {
    304   CloseCurrentSessions(ERR_CERT_DATABASE_CHANGED);
    305 }
    306 
    307 void SpdySessionPool::OnCACertChanged(const X509Certificate* cert) {
    308   // Per wtc, we actually only need to CloseCurrentSessions when trust is
    309   // reduced. CloseCurrentSessions now because OnCACertChanged does not
    310   // tell us this.
    311   // See comments in ClientSocketPoolManager::OnCACertChanged.
    312   CloseCurrentSessions(ERR_CERT_DATABASE_CHANGED);
    313 }
    314 
    315 bool SpdySessionPool::IsSessionAvailable(
    316     const base::WeakPtr<SpdySession>& session) const {
    317   for (AvailableSessionMap::const_iterator it = available_sessions_.begin();
    318        it != available_sessions_.end(); ++it) {
    319     if (it->second.get() == session.get())
    320       return true;
    321   }
    322   return false;
    323 }
    324 
    325 const SpdySessionKey& SpdySessionPool::NormalizeListKey(
    326     const SpdySessionKey& key) const {
    327   if (!force_single_domain_)
    328     return key;
    329 
    330   static SpdySessionKey* single_domain_key = NULL;
    331   if (!single_domain_key) {
    332     HostPortPair single_domain = HostPortPair("singledomain.com", 80);
    333     single_domain_key = new SpdySessionKey(single_domain,
    334                                            ProxyServer::Direct(),
    335                                            PRIVACY_MODE_DISABLED);
    336   }
    337   return *single_domain_key;
    338 }
    339 
    340 void SpdySessionPool::MapKeyToAvailableSession(
    341     const SpdySessionKey& key,
    342     const base::WeakPtr<SpdySession>& session) {
    343   DCHECK(ContainsKey(sessions_, session.get()));
    344   const SpdySessionKey& normalized_key = NormalizeListKey(key);
    345   std::pair<AvailableSessionMap::iterator, bool> result =
    346       available_sessions_.insert(std::make_pair(normalized_key, session));
    347   CHECK(result.second);
    348 }
    349 
    350 SpdySessionPool::AvailableSessionMap::iterator
    351 SpdySessionPool::LookupAvailableSessionByKey(
    352     const SpdySessionKey& key) {
    353   const SpdySessionKey& normalized_key = NormalizeListKey(key);
    354   return available_sessions_.find(normalized_key);
    355 }
    356 
    357 void SpdySessionPool::UnmapKey(const SpdySessionKey& key) {
    358   AvailableSessionMap::iterator it = LookupAvailableSessionByKey(key);
    359   CHECK(it != available_sessions_.end());
    360   available_sessions_.erase(it);
    361 }
    362 
    363 void SpdySessionPool::RemoveAliases(const SpdySessionKey& key) {
    364   // Walk the aliases map, find references to this pair.
    365   // TODO(mbelshe):  Figure out if this is too expensive.
    366   for (AliasMap::iterator it = aliases_.begin(); it != aliases_.end(); ) {
    367     if (it->second.Equals(key)) {
    368       AliasMap::iterator old_it = it;
    369       ++it;
    370       aliases_.erase(old_it);
    371     } else {
    372       ++it;
    373     }
    374   }
    375 }
    376 
    377 SpdySessionPool::WeakSessionList SpdySessionPool::GetCurrentSessions() const {
    378   WeakSessionList current_sessions;
    379   for (SessionSet::const_iterator it = sessions_.begin();
    380        it != sessions_.end(); ++it) {
    381     current_sessions.push_back((*it)->GetWeakPtr());
    382   }
    383   return current_sessions;
    384 }
    385 
    386 void SpdySessionPool::CloseCurrentSessionsHelper(
    387     Error error,
    388     const std::string& description,
    389     bool idle_only) {
    390   WeakSessionList current_sessions = GetCurrentSessions();
    391   for (WeakSessionList::const_iterator it = current_sessions.begin();
    392        it != current_sessions.end(); ++it) {
    393     if (!*it)
    394       continue;
    395 
    396     if (idle_only && (*it)->is_active())
    397       continue;
    398 
    399     (*it)->CloseSessionOnError(error, description);
    400     DCHECK(!IsSessionAvailable(*it));
    401   }
    402 }
    403 
    404 }  // namespace net
    405