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