OLD | NEW |
| (Empty) |
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 <utility> | |
8 | |
9 #include "base/logging.h" | |
10 #include "base/memory/ptr_util.h" | |
11 #include "base/metrics/histogram_macros.h" | |
12 #include "base/profiler/scoped_tracker.h" | |
13 #include "base/stl_util.h" | |
14 #include "base/trace_event/memory_allocator_dump.h" | |
15 #include "base/trace_event/process_memory_dump.h" | |
16 #include "base/trace_event/trace_event.h" | |
17 #include "base/values.h" | |
18 #include "net/base/address_list.h" | |
19 #include "net/base/trace_constants.h" | |
20 #include "net/http/http_network_session.h" | |
21 #include "net/http/http_server_properties.h" | |
22 #include "net/log/net_log_event_type.h" | |
23 #include "net/log/net_log_source.h" | |
24 #include "net/log/net_log_with_source.h" | |
25 #include "net/spdy/hpack/hpack_constants.h" | |
26 #include "net/spdy/hpack/hpack_huffman_table.h" | |
27 #include "net/spdy/hpack/hpack_static_table.h" | |
28 #include "net/spdy/platform/api/spdy_estimate_memory_usage.h" | |
29 #include "net/spdy/platform/api/spdy_string_utils.h" | |
30 #include "net/spdy/spdy_session.h" | |
31 | |
32 namespace net { | |
33 | |
34 namespace { | |
35 | |
36 enum SpdySessionGetTypes { | |
37 CREATED_NEW = 0, | |
38 FOUND_EXISTING = 1, | |
39 FOUND_EXISTING_FROM_IP_POOL = 2, | |
40 IMPORTED_FROM_SOCKET = 3, | |
41 SPDY_SESSION_GET_MAX = 4 | |
42 }; | |
43 | |
44 } // namespace | |
45 | |
46 SpdySessionPool::SpdySessionPool( | |
47 HostResolver* resolver, | |
48 SSLConfigService* ssl_config_service, | |
49 HttpServerProperties* http_server_properties, | |
50 TransportSecurityState* transport_security_state, | |
51 bool enable_ping_based_connection_checking, | |
52 size_t session_max_recv_window_size, | |
53 const SettingsMap& initial_settings, | |
54 SpdySessionPool::TimeFunc time_func, | |
55 ProxyDelegate* proxy_delegate) | |
56 : http_server_properties_(http_server_properties), | |
57 transport_security_state_(transport_security_state), | |
58 ssl_config_service_(ssl_config_service), | |
59 resolver_(resolver), | |
60 enable_sending_initial_data_(true), | |
61 enable_ping_based_connection_checking_( | |
62 enable_ping_based_connection_checking), | |
63 session_max_recv_window_size_(session_max_recv_window_size), | |
64 initial_settings_(initial_settings), | |
65 time_func_(time_func), | |
66 push_delegate_(nullptr), | |
67 proxy_delegate_(proxy_delegate) { | |
68 NetworkChangeNotifier::AddIPAddressObserver(this); | |
69 if (ssl_config_service_.get()) | |
70 ssl_config_service_->AddObserver(this); | |
71 CertDatabase::GetInstance()->AddObserver(this); | |
72 } | |
73 | |
74 SpdySessionPool::~SpdySessionPool() { | |
75 CloseAllSessions(); | |
76 | |
77 while (!sessions_.empty()) { | |
78 // Destroy sessions to enforce that lifetime is scoped to SpdySessionPool. | |
79 // Write callbacks queued upon session drain are not invoked. | |
80 RemoveUnavailableSession((*sessions_.begin())->GetWeakPtr()); | |
81 } | |
82 | |
83 if (ssl_config_service_.get()) | |
84 ssl_config_service_->RemoveObserver(this); | |
85 NetworkChangeNotifier::RemoveIPAddressObserver(this); | |
86 CertDatabase::GetInstance()->RemoveObserver(this); | |
87 } | |
88 | |
89 base::WeakPtr<SpdySession> SpdySessionPool::CreateAvailableSessionFromSocket( | |
90 const SpdySessionKey& key, | |
91 std::unique_ptr<ClientSocketHandle> connection, | |
92 const NetLogWithSource& net_log, | |
93 bool is_secure) { | |
94 TRACE_EVENT0(kNetTracingCategory, | |
95 "SpdySessionPool::CreateAvailableSessionFromSocket"); | |
96 | |
97 UMA_HISTOGRAM_ENUMERATION( | |
98 "Net.SpdySessionGet", IMPORTED_FROM_SOCKET, SPDY_SESSION_GET_MAX); | |
99 | |
100 auto new_session = base::MakeUnique<SpdySession>( | |
101 key, http_server_properties_, transport_security_state_, | |
102 enable_sending_initial_data_, enable_ping_based_connection_checking_, | |
103 session_max_recv_window_size_, initial_settings_, time_func_, | |
104 push_delegate_, proxy_delegate_, net_log.net_log()); | |
105 | |
106 new_session->InitializeWithSocket(std::move(connection), this, is_secure); | |
107 | |
108 base::WeakPtr<SpdySession> available_session = new_session->GetWeakPtr(); | |
109 sessions_.insert(new_session.release()); | |
110 MapKeyToAvailableSession(key, available_session); | |
111 | |
112 net_log.AddEvent( | |
113 NetLogEventType::HTTP2_SESSION_POOL_IMPORTED_SESSION_FROM_SOCKET, | |
114 available_session->net_log().source().ToEventParametersCallback()); | |
115 | |
116 // Look up the IP address for this session so that we can match | |
117 // future sessions (potentially to different domains) which can | |
118 // potentially be pooled with this one. Because GetPeerAddress() | |
119 // reports the proxy's address instead of the origin server, check | |
120 // to see if this is a direct connection. | |
121 if (key.proxy_server().is_direct()) { | |
122 IPEndPoint address; | |
123 if (available_session->GetPeerAddress(&address) == OK) | |
124 aliases_[address] = key; | |
125 } | |
126 | |
127 return available_session; | |
128 } | |
129 | |
130 base::WeakPtr<SpdySession> SpdySessionPool::FindAvailableSession( | |
131 const SpdySessionKey& key, | |
132 const GURL& url, | |
133 bool enable_ip_based_pooling, | |
134 const NetLogWithSource& net_log) { | |
135 UnclaimedPushedStreamMap::iterator url_it = | |
136 unclaimed_pushed_streams_.find(url); | |
137 if (!url.is_empty() && url_it != unclaimed_pushed_streams_.end()) { | |
138 DCHECK(url.SchemeIsCryptographic()); | |
139 for (WeakSessionList::iterator it = url_it->second.begin(); | |
140 it != url_it->second.end();) { | |
141 base::WeakPtr<SpdySession> spdy_session = *it; | |
142 // Lazy deletion of destroyed SpdySessions. | |
143 if (!spdy_session) { | |
144 it = url_it->second.erase(it); | |
145 continue; | |
146 } | |
147 ++it; | |
148 const SpdySessionKey& spdy_session_key = spdy_session->spdy_session_key(); | |
149 if (!(spdy_session_key.proxy_server() == key.proxy_server()) || | |
150 !(spdy_session_key.privacy_mode() == key.privacy_mode())) { | |
151 continue; | |
152 } | |
153 if (!spdy_session->VerifyDomainAuthentication( | |
154 key.host_port_pair().host())) { | |
155 continue; | |
156 } | |
157 return spdy_session; | |
158 } | |
159 if (url_it->second.empty()) { | |
160 unclaimed_pushed_streams_.erase(url_it); | |
161 } | |
162 } | |
163 | |
164 AvailableSessionMap::iterator it = LookupAvailableSessionByKey(key); | |
165 if (it != available_sessions_.end()) { | |
166 if (key.Equals(it->second->spdy_session_key())) { | |
167 UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", FOUND_EXISTING, | |
168 SPDY_SESSION_GET_MAX); | |
169 net_log.AddEvent( | |
170 NetLogEventType::HTTP2_SESSION_POOL_FOUND_EXISTING_SESSION, | |
171 it->second->net_log().source().ToEventParametersCallback()); | |
172 } else { | |
173 if (!enable_ip_based_pooling) { | |
174 // Remove session from available sessions and from aliases, and remove | |
175 // key from the session's pooled alias set, so that a new session can be | |
176 // created with this |key|. | |
177 it->second->RemovePooledAlias(key); | |
178 UnmapKey(key); | |
179 RemoveAliases(key); | |
180 return base::WeakPtr<SpdySession>(); | |
181 } | |
182 | |
183 UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", | |
184 FOUND_EXISTING_FROM_IP_POOL, | |
185 SPDY_SESSION_GET_MAX); | |
186 net_log.AddEvent( | |
187 NetLogEventType:: | |
188 HTTP2_SESSION_POOL_FOUND_EXISTING_SESSION_FROM_IP_POOL, | |
189 it->second->net_log().source().ToEventParametersCallback()); | |
190 } | |
191 return it->second; | |
192 } | |
193 | |
194 if (!enable_ip_based_pooling) | |
195 return base::WeakPtr<SpdySession>(); | |
196 | |
197 // Look up IP addresses from resolver cache. | |
198 HostResolver::RequestInfo resolve_info(key.host_port_pair()); | |
199 AddressList addresses; | |
200 int rv = resolver_->ResolveFromCache(resolve_info, &addresses, net_log); | |
201 DCHECK_NE(rv, ERR_IO_PENDING); | |
202 if (rv != OK) | |
203 return base::WeakPtr<SpdySession>(); | |
204 | |
205 // Check if we have a session through a domain alias. | |
206 for (AddressList::const_iterator address_it = addresses.begin(); | |
207 address_it != addresses.end(); | |
208 ++address_it) { | |
209 AliasMap::const_iterator alias_it = aliases_.find(*address_it); | |
210 if (alias_it == aliases_.end()) | |
211 continue; | |
212 | |
213 // We found an alias. | |
214 const SpdySessionKey& alias_key = alias_it->second; | |
215 | |
216 // We can reuse this session only if the proxy and privacy | |
217 // settings match. | |
218 if (!(alias_key.proxy_server() == key.proxy_server()) || | |
219 !(alias_key.privacy_mode() == key.privacy_mode())) | |
220 continue; | |
221 | |
222 AvailableSessionMap::iterator available_session_it = | |
223 LookupAvailableSessionByKey(alias_key); | |
224 if (available_session_it == available_sessions_.end()) { | |
225 NOTREACHED(); // It shouldn't be in the aliases table if we can't get it! | |
226 continue; | |
227 } | |
228 | |
229 const base::WeakPtr<SpdySession>& available_session = | |
230 available_session_it->second; | |
231 DCHECK(base::ContainsKey(sessions_, available_session.get())); | |
232 // If the session is a secure one, we need to verify that the | |
233 // server is authenticated to serve traffic for |host_port_proxy_pair| too. | |
234 if (!available_session->VerifyDomainAuthentication( | |
235 key.host_port_pair().host())) { | |
236 UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 0, 2); | |
237 continue; | |
238 } | |
239 | |
240 UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 1, 2); | |
241 UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", | |
242 FOUND_EXISTING_FROM_IP_POOL, | |
243 SPDY_SESSION_GET_MAX); | |
244 net_log.AddEvent( | |
245 NetLogEventType::HTTP2_SESSION_POOL_FOUND_EXISTING_SESSION_FROM_IP_POOL, | |
246 available_session->net_log().source().ToEventParametersCallback()); | |
247 // Add this session to the map so that we can find it next time. | |
248 MapKeyToAvailableSession(key, available_session); | |
249 available_session->AddPooledAlias(key); | |
250 return available_session; | |
251 } | |
252 | |
253 return base::WeakPtr<SpdySession>(); | |
254 } | |
255 | |
256 void SpdySessionPool::MakeSessionUnavailable( | |
257 const base::WeakPtr<SpdySession>& available_session) { | |
258 UnmapKey(available_session->spdy_session_key()); | |
259 RemoveAliases(available_session->spdy_session_key()); | |
260 const std::set<SpdySessionKey>& aliases = available_session->pooled_aliases(); | |
261 for (std::set<SpdySessionKey>::const_iterator it = aliases.begin(); | |
262 it != aliases.end(); ++it) { | |
263 UnmapKey(*it); | |
264 RemoveAliases(*it); | |
265 } | |
266 DCHECK(!IsSessionAvailable(available_session)); | |
267 } | |
268 | |
269 void SpdySessionPool::RemoveUnavailableSession( | |
270 const base::WeakPtr<SpdySession>& unavailable_session) { | |
271 DCHECK(!IsSessionAvailable(unavailable_session)); | |
272 | |
273 unavailable_session->net_log().AddEvent( | |
274 NetLogEventType::HTTP2_SESSION_POOL_REMOVE_SESSION); | |
275 | |
276 SessionSet::iterator it = sessions_.find(unavailable_session.get()); | |
277 CHECK(it != sessions_.end()); | |
278 std::unique_ptr<SpdySession> owned_session(*it); | |
279 sessions_.erase(it); | |
280 } | |
281 | |
282 // Make a copy of |sessions_| in the Close* functions below to avoid | |
283 // reentrancy problems. Since arbitrary functions get called by close | |
284 // handlers, it doesn't suffice to simply increment the iterator | |
285 // before closing. | |
286 | |
287 void SpdySessionPool::CloseCurrentSessions(Error error) { | |
288 CloseCurrentSessionsHelper(error, "Closing current sessions.", | |
289 false /* idle_only */); | |
290 } | |
291 | |
292 void SpdySessionPool::CloseCurrentIdleSessions() { | |
293 CloseCurrentSessionsHelper(ERR_ABORTED, "Closing idle sessions.", | |
294 true /* idle_only */); | |
295 } | |
296 | |
297 void SpdySessionPool::CloseAllSessions() { | |
298 while (!available_sessions_.empty()) { | |
299 CloseCurrentSessionsHelper(ERR_ABORTED, "Closing all sessions.", | |
300 false /* idle_only */); | |
301 } | |
302 } | |
303 | |
304 void SpdySessionPool::RegisterUnclaimedPushedStream( | |
305 GURL url, | |
306 base::WeakPtr<SpdySession> spdy_session) { | |
307 DCHECK(!url.is_empty()); | |
308 // This SpdySessionPool must own |spdy_session|. | |
309 DCHECK(base::ContainsKey(sessions_, spdy_session.get())); | |
310 UnclaimedPushedStreamMap::iterator url_it = | |
311 unclaimed_pushed_streams_.lower_bound(url); | |
312 if (url_it == unclaimed_pushed_streams_.end() || url_it->first != url) { | |
313 WeakSessionList list; | |
314 list.push_back(std::move(spdy_session)); | |
315 UnclaimedPushedStreamMap::value_type value(std::move(url), std::move(list)); | |
316 unclaimed_pushed_streams_.insert(url_it, std::move(value)); | |
317 return; | |
318 } | |
319 url_it->second.push_back(spdy_session); | |
320 } | |
321 | |
322 void SpdySessionPool::UnregisterUnclaimedPushedStream( | |
323 const GURL& url, | |
324 SpdySession* spdy_session) { | |
325 DCHECK(!url.is_empty()); | |
326 UnclaimedPushedStreamMap::iterator url_it = | |
327 unclaimed_pushed_streams_.find(url); | |
328 DCHECK(url_it != unclaimed_pushed_streams_.end()); | |
329 size_t removed = 0; | |
330 for (WeakSessionList::iterator it = url_it->second.begin(); | |
331 it != url_it->second.end();) { | |
332 // Lazy deletion of destroyed SpdySessions. | |
333 if (!*it) { | |
334 it = url_it->second.erase(it); | |
335 continue; | |
336 } | |
337 if (it->get() == spdy_session) { | |
338 it = url_it->second.erase(it); | |
339 ++removed; | |
340 break; | |
341 } | |
342 ++it; | |
343 } | |
344 if (url_it->second.empty()) { | |
345 unclaimed_pushed_streams_.erase(url_it); | |
346 } | |
347 DCHECK_EQ(1u, removed); | |
348 } | |
349 | |
350 std::unique_ptr<base::Value> SpdySessionPool::SpdySessionPoolInfoToValue() | |
351 const { | |
352 std::unique_ptr<base::ListValue> list(new base::ListValue()); | |
353 | |
354 for (AvailableSessionMap::const_iterator it = available_sessions_.begin(); | |
355 it != available_sessions_.end(); ++it) { | |
356 // Only add the session if the key in the map matches the main | |
357 // host_port_proxy_pair (not an alias). | |
358 const SpdySessionKey& key = it->first; | |
359 const SpdySessionKey& session_key = it->second->spdy_session_key(); | |
360 if (key.Equals(session_key)) | |
361 list->Append(it->second->GetInfoAsValue()); | |
362 } | |
363 return std::move(list); | |
364 } | |
365 | |
366 void SpdySessionPool::OnIPAddressChanged() { | |
367 WeakSessionList current_sessions = GetCurrentSessions(); | |
368 for (WeakSessionList::const_iterator it = current_sessions.begin(); | |
369 it != current_sessions.end(); ++it) { | |
370 if (!*it) | |
371 continue; | |
372 | |
373 // For OSs that terminate TCP connections upon relevant network changes, | |
374 // attempt to preserve active streams by marking all sessions as going | |
375 // away, rather than explicitly closing them. Streams may still fail due | |
376 // to a generated TCP reset. | |
377 #if defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_IOS) | |
378 (*it)->MakeUnavailable(); | |
379 (*it)->StartGoingAway(kLastStreamId, ERR_NETWORK_CHANGED); | |
380 (*it)->MaybeFinishGoingAway(); | |
381 #else | |
382 (*it)->CloseSessionOnError(ERR_NETWORK_CHANGED, | |
383 "Closing current sessions."); | |
384 DCHECK((*it)->IsDraining()); | |
385 #endif // defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_IOS) | |
386 DCHECK(!IsSessionAvailable(*it)); | |
387 } | |
388 } | |
389 | |
390 void SpdySessionPool::OnSSLConfigChanged() { | |
391 CloseCurrentSessions(ERR_NETWORK_CHANGED); | |
392 } | |
393 | |
394 void SpdySessionPool::OnCertDBChanged() { | |
395 CloseCurrentSessions(ERR_CERT_DATABASE_CHANGED); | |
396 } | |
397 | |
398 void SpdySessionPool::DumpMemoryStats( | |
399 base::trace_event::ProcessMemoryDump* pmd, | |
400 const SpdyString& parent_dump_absolute_name) const { | |
401 if (sessions_.empty()) | |
402 return; | |
403 size_t total_size = 0; | |
404 size_t buffer_size = 0; | |
405 size_t cert_count = 0; | |
406 size_t cert_size = 0; | |
407 size_t num_active_sessions = 0; | |
408 for (auto* session : sessions_) { | |
409 StreamSocket::SocketMemoryStats stats; | |
410 bool is_session_active = false; | |
411 total_size += session->DumpMemoryStats(&stats, &is_session_active); | |
412 buffer_size += stats.buffer_size; | |
413 cert_count += stats.cert_count; | |
414 cert_size += stats.cert_size; | |
415 if (is_session_active) | |
416 num_active_sessions++; | |
417 } | |
418 total_size += SpdyEstimateMemoryUsage(ObtainHpackHuffmanTable()) + | |
419 SpdyEstimateMemoryUsage(ObtainHpackStaticTable()); | |
420 base::trace_event::MemoryAllocatorDump* dump = | |
421 pmd->CreateAllocatorDump(SpdyStringPrintf( | |
422 "%s/spdy_session_pool", parent_dump_absolute_name.c_str())); | |
423 dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize, | |
424 base::trace_event::MemoryAllocatorDump::kUnitsBytes, | |
425 total_size); | |
426 dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameObjectCount, | |
427 base::trace_event::MemoryAllocatorDump::kUnitsObjects, | |
428 sessions_.size()); | |
429 dump->AddScalar("active_session_count", | |
430 base::trace_event::MemoryAllocatorDump::kUnitsObjects, | |
431 num_active_sessions); | |
432 dump->AddScalar("buffer_size", | |
433 base::trace_event::MemoryAllocatorDump::kUnitsBytes, | |
434 buffer_size); | |
435 dump->AddScalar("cert_count", | |
436 base::trace_event::MemoryAllocatorDump::kUnitsObjects, | |
437 cert_count); | |
438 dump->AddScalar("cert_size", | |
439 base::trace_event::MemoryAllocatorDump::kUnitsBytes, | |
440 cert_size); | |
441 } | |
442 | |
443 bool SpdySessionPool::IsSessionAvailable( | |
444 const base::WeakPtr<SpdySession>& session) const { | |
445 for (AvailableSessionMap::const_iterator it = available_sessions_.begin(); | |
446 it != available_sessions_.end(); ++it) { | |
447 if (it->second.get() == session.get()) | |
448 return true; | |
449 } | |
450 return false; | |
451 } | |
452 | |
453 void SpdySessionPool::MapKeyToAvailableSession( | |
454 const SpdySessionKey& key, | |
455 const base::WeakPtr<SpdySession>& session) { | |
456 DCHECK(base::ContainsKey(sessions_, session.get())); | |
457 std::pair<AvailableSessionMap::iterator, bool> result = | |
458 available_sessions_.insert(std::make_pair(key, session)); | |
459 CHECK(result.second); | |
460 } | |
461 | |
462 SpdySessionPool::AvailableSessionMap::iterator | |
463 SpdySessionPool::LookupAvailableSessionByKey( | |
464 const SpdySessionKey& key) { | |
465 return available_sessions_.find(key); | |
466 } | |
467 | |
468 void SpdySessionPool::UnmapKey(const SpdySessionKey& key) { | |
469 AvailableSessionMap::iterator it = LookupAvailableSessionByKey(key); | |
470 CHECK(it != available_sessions_.end()); | |
471 available_sessions_.erase(it); | |
472 } | |
473 | |
474 void SpdySessionPool::RemoveAliases(const SpdySessionKey& key) { | |
475 // Walk the aliases map, find references to this pair. | |
476 // TODO(mbelshe): Figure out if this is too expensive. | |
477 for (AliasMap::iterator it = aliases_.begin(); it != aliases_.end(); ) { | |
478 if (it->second.Equals(key)) { | |
479 AliasMap::iterator old_it = it; | |
480 ++it; | |
481 aliases_.erase(old_it); | |
482 } else { | |
483 ++it; | |
484 } | |
485 } | |
486 } | |
487 | |
488 SpdySessionPool::WeakSessionList SpdySessionPool::GetCurrentSessions() const { | |
489 WeakSessionList current_sessions; | |
490 for (SessionSet::const_iterator it = sessions_.begin(); | |
491 it != sessions_.end(); ++it) { | |
492 current_sessions.push_back((*it)->GetWeakPtr()); | |
493 } | |
494 return current_sessions; | |
495 } | |
496 | |
497 void SpdySessionPool::CloseCurrentSessionsHelper(Error error, | |
498 const SpdyString& description, | |
499 bool idle_only) { | |
500 WeakSessionList current_sessions = GetCurrentSessions(); | |
501 for (WeakSessionList::const_iterator it = current_sessions.begin(); | |
502 it != current_sessions.end(); ++it) { | |
503 if (!*it) | |
504 continue; | |
505 | |
506 if (idle_only && (*it)->is_active()) | |
507 continue; | |
508 | |
509 (*it)->CloseSessionOnError(error, description); | |
510 DCHECK(!IsSessionAvailable(*it)); | |
511 } | |
512 } | |
513 | |
514 } // namespace net | |
OLD | NEW |