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