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/socket/client_socket_pool_manager.h" | |
6 | |
7 #include <string> | |
8 | |
9 #include "base/basictypes.h" | |
10 #include "base/logging.h" | |
11 #include "base/strings/stringprintf.h" | |
12 #include "net/base/load_flags.h" | |
13 #include "net/http/http_proxy_client_socket_pool.h" | |
14 #include "net/http/http_request_info.h" | |
15 #include "net/http/http_stream_factory.h" | |
16 #include "net/proxy/proxy_info.h" | |
17 #include "net/socket/client_socket_handle.h" | |
18 #include "net/socket/socks_client_socket_pool.h" | |
19 #include "net/socket/ssl_client_socket_pool.h" | |
20 #include "net/socket/transport_client_socket_pool.h" | |
21 | |
22 namespace net { | |
23 | |
24 namespace { | |
25 | |
26 // Limit of sockets of each socket pool. | |
27 int g_max_sockets_per_pool[] = { | |
28 256, // NORMAL_SOCKET_POOL | |
29 256 // WEBSOCKET_SOCKET_POOL | |
30 }; | |
31 | |
32 static_assert(arraysize(g_max_sockets_per_pool) == | |
33 HttpNetworkSession::NUM_SOCKET_POOL_TYPES, | |
34 "max sockets per pool length mismatch"); | |
35 | |
36 // Default to allow up to 6 connections per host. Experiment and tuning may | |
37 // try other values (greater than 0). Too large may cause many problems, such | |
38 // as home routers blocking the connections!?!? See http://crbug.com/12066. | |
39 // | |
40 // WebSocket connections are long-lived, and should be treated differently | |
41 // than normal other connections. 6 connections per group sounded too small | |
42 // for such use, thus we use a larger limit which was determined somewhat | |
43 // arbitrarily. | |
44 // TODO(yutak): Look at the usage and determine the right value after | |
45 // WebSocket protocol stack starts to work. | |
46 int g_max_sockets_per_group[] = { | |
47 6, // NORMAL_SOCKET_POOL | |
48 30 // WEBSOCKET_SOCKET_POOL | |
49 }; | |
50 | |
51 static_assert(arraysize(g_max_sockets_per_group) == | |
52 HttpNetworkSession::NUM_SOCKET_POOL_TYPES, | |
53 "max sockets per group length mismatch"); | |
54 | |
55 // The max number of sockets to allow per proxy server. This applies both to | |
56 // http and SOCKS proxies. See http://crbug.com/12066 and | |
57 // http://crbug.com/44501 for details about proxy server connection limits. | |
58 int g_max_sockets_per_proxy_server[] = { | |
59 kDefaultMaxSocketsPerProxyServer, // NORMAL_SOCKET_POOL | |
60 kDefaultMaxSocketsPerProxyServer // WEBSOCKET_SOCKET_POOL | |
61 }; | |
62 | |
63 static_assert(arraysize(g_max_sockets_per_proxy_server) == | |
64 HttpNetworkSession::NUM_SOCKET_POOL_TYPES, | |
65 "max sockets per proxy server length mismatch"); | |
66 | |
67 // The meat of the implementation for the InitSocketHandleForHttpRequest, | |
68 // InitSocketHandleForRawConnect and PreconnectSocketsForHttpRequest methods. | |
69 int InitSocketPoolHelper(const GURL& request_url, | |
70 const HttpRequestHeaders& request_extra_headers, | |
71 int request_load_flags, | |
72 RequestPriority request_priority, | |
73 HttpNetworkSession* session, | |
74 const ProxyInfo& proxy_info, | |
75 bool force_spdy_over_ssl, | |
76 bool want_spdy_over_npn, | |
77 const SSLConfig& ssl_config_for_origin, | |
78 const SSLConfig& ssl_config_for_proxy, | |
79 bool force_tunnel, | |
80 PrivacyMode privacy_mode, | |
81 const BoundNetLog& net_log, | |
82 int num_preconnect_streams, | |
83 ClientSocketHandle* socket_handle, | |
84 HttpNetworkSession::SocketPoolType socket_pool_type, | |
85 const OnHostResolutionCallback& resolution_callback, | |
86 const CompletionCallback& callback) { | |
87 scoped_refptr<HttpProxySocketParams> http_proxy_params; | |
88 scoped_refptr<SOCKSSocketParams> socks_params; | |
89 scoped_ptr<HostPortPair> proxy_host_port; | |
90 | |
91 bool using_ssl = request_url.SchemeIs("https") || | |
92 request_url.SchemeIs("wss") || force_spdy_over_ssl; | |
93 | |
94 HostPortPair origin_host_port = HostPortPair::FromURL(request_url); | |
95 | |
96 if (!using_ssl && session->params().testing_fixed_http_port != 0) { | |
97 origin_host_port.set_port(session->params().testing_fixed_http_port); | |
98 } else if (using_ssl && session->params().testing_fixed_https_port != 0) { | |
99 origin_host_port.set_port(session->params().testing_fixed_https_port); | |
100 } | |
101 | |
102 bool disable_resolver_cache = | |
103 request_load_flags & LOAD_BYPASS_CACHE || | |
104 request_load_flags & LOAD_VALIDATE_CACHE || | |
105 request_load_flags & LOAD_DISABLE_CACHE; | |
106 | |
107 int load_flags = request_load_flags; | |
108 if (session->params().ignore_certificate_errors) | |
109 load_flags |= LOAD_IGNORE_ALL_CERT_ERRORS; | |
110 | |
111 // Build the string used to uniquely identify connections of this type. | |
112 // Determine the host and port to connect to. | |
113 std::string connection_group = origin_host_port.ToString(); | |
114 DCHECK(!connection_group.empty()); | |
115 if (request_url.SchemeIs("ftp")) { | |
116 // Combining FTP with forced SPDY over SSL would be a "path to madness". | |
117 // Make sure we never do that. | |
118 DCHECK(!using_ssl); | |
119 connection_group = "ftp/" + connection_group; | |
120 } | |
121 if (using_ssl) { | |
122 // All connections in a group should use the same SSLConfig settings. | |
123 // Encode version_max in the connection group's name, unless it's the | |
124 // default version_max. (We want the common case to use the shortest | |
125 // encoding). A version_max of TLS 1.1 is encoded as "ssl(max:3.2)/" | |
126 // rather than "tlsv1.1/" because the actual protocol version, which | |
127 // is selected by the server, may not be TLS 1.1. Do not encode | |
128 // version_min in the connection group's name because version_min | |
129 // should be the same for all connections, whereas version_max may | |
130 // change for version fallbacks. | |
131 std::string prefix = "ssl/"; | |
132 if (ssl_config_for_origin.version_max != | |
133 SSLClientSocket::GetMaxSupportedSSLVersion()) { | |
134 switch (ssl_config_for_origin.version_max) { | |
135 case SSL_PROTOCOL_VERSION_TLS1_2: | |
136 prefix = "ssl(max:3.3)/"; | |
137 break; | |
138 case SSL_PROTOCOL_VERSION_TLS1_1: | |
139 prefix = "ssl(max:3.2)/"; | |
140 break; | |
141 case SSL_PROTOCOL_VERSION_TLS1: | |
142 prefix = "ssl(max:3.1)/"; | |
143 break; | |
144 case SSL_PROTOCOL_VERSION_SSL3: | |
145 prefix = "sslv3/"; | |
146 break; | |
147 default: | |
148 CHECK(false); | |
149 break; | |
150 } | |
151 } | |
152 connection_group = prefix + connection_group; | |
153 } | |
154 | |
155 bool ignore_limits = (request_load_flags & LOAD_IGNORE_LIMITS) != 0; | |
156 if (!proxy_info.is_direct()) { | |
157 ProxyServer proxy_server = proxy_info.proxy_server(); | |
158 proxy_host_port.reset(new HostPortPair(proxy_server.host_port_pair())); | |
159 scoped_refptr<TransportSocketParams> proxy_tcp_params( | |
160 new TransportSocketParams( | |
161 *proxy_host_port, | |
162 disable_resolver_cache, | |
163 ignore_limits, | |
164 resolution_callback, | |
165 TransportSocketParams::COMBINE_CONNECT_AND_WRITE_DEFAULT)); | |
166 | |
167 if (proxy_info.is_http() || proxy_info.is_https()) { | |
168 std::string user_agent; | |
169 request_extra_headers.GetHeader(HttpRequestHeaders::kUserAgent, | |
170 &user_agent); | |
171 scoped_refptr<SSLSocketParams> ssl_params; | |
172 if (proxy_info.is_https()) { | |
173 // Combine connect and write for SSL sockets in TCP FastOpen | |
174 // field trial. | |
175 TransportSocketParams::CombineConnectAndWritePolicy | |
176 combine_connect_and_write = | |
177 session->params().enable_tcp_fast_open_for_ssl ? | |
178 TransportSocketParams::COMBINE_CONNECT_AND_WRITE_DESIRED : | |
179 TransportSocketParams::COMBINE_CONNECT_AND_WRITE_DEFAULT; | |
180 proxy_tcp_params = new TransportSocketParams(*proxy_host_port, | |
181 disable_resolver_cache, | |
182 ignore_limits, | |
183 resolution_callback, | |
184 combine_connect_and_write); | |
185 // Set ssl_params, and unset proxy_tcp_params | |
186 ssl_params = new SSLSocketParams(proxy_tcp_params, | |
187 NULL, | |
188 NULL, | |
189 *proxy_host_port.get(), | |
190 ssl_config_for_proxy, | |
191 PRIVACY_MODE_DISABLED, | |
192 load_flags, | |
193 force_spdy_over_ssl, | |
194 want_spdy_over_npn); | |
195 proxy_tcp_params = NULL; | |
196 } | |
197 | |
198 http_proxy_params = | |
199 new HttpProxySocketParams(proxy_tcp_params, | |
200 ssl_params, | |
201 request_url, | |
202 user_agent, | |
203 origin_host_port, | |
204 session->http_auth_cache(), | |
205 session->http_auth_handler_factory(), | |
206 session->spdy_session_pool(), | |
207 force_tunnel || using_ssl, | |
208 session->params().proxy_delegate); | |
209 } else { | |
210 DCHECK(proxy_info.is_socks()); | |
211 char socks_version; | |
212 if (proxy_server.scheme() == ProxyServer::SCHEME_SOCKS5) | |
213 socks_version = '5'; | |
214 else | |
215 socks_version = '4'; | |
216 connection_group = base::StringPrintf( | |
217 "socks%c/%s", socks_version, connection_group.c_str()); | |
218 | |
219 socks_params = new SOCKSSocketParams(proxy_tcp_params, | |
220 socks_version == '5', | |
221 origin_host_port); | |
222 } | |
223 } | |
224 | |
225 // Change group name if privacy mode is enabled. | |
226 if (privacy_mode == PRIVACY_MODE_ENABLED) | |
227 connection_group = "pm/" + connection_group; | |
228 | |
229 // Deal with SSL - which layers on top of any given proxy. | |
230 if (using_ssl) { | |
231 scoped_refptr<TransportSocketParams> ssl_tcp_params; | |
232 if (proxy_info.is_direct()) { | |
233 // Setup TCP params if non-proxied SSL connection. | |
234 // Combine connect and write for SSL sockets in TCP FastOpen field trial. | |
235 TransportSocketParams::CombineConnectAndWritePolicy | |
236 combine_connect_and_write = | |
237 session->params().enable_tcp_fast_open_for_ssl ? | |
238 TransportSocketParams::COMBINE_CONNECT_AND_WRITE_DESIRED : | |
239 TransportSocketParams::COMBINE_CONNECT_AND_WRITE_DEFAULT; | |
240 ssl_tcp_params = new TransportSocketParams(origin_host_port, | |
241 disable_resolver_cache, | |
242 ignore_limits, | |
243 resolution_callback, | |
244 combine_connect_and_write); | |
245 } | |
246 scoped_refptr<SSLSocketParams> ssl_params = | |
247 new SSLSocketParams(ssl_tcp_params, | |
248 socks_params, | |
249 http_proxy_params, | |
250 origin_host_port, | |
251 ssl_config_for_origin, | |
252 privacy_mode, | |
253 load_flags, | |
254 force_spdy_over_ssl, | |
255 want_spdy_over_npn); | |
256 SSLClientSocketPool* ssl_pool = NULL; | |
257 if (proxy_info.is_direct()) { | |
258 ssl_pool = session->GetSSLSocketPool(socket_pool_type); | |
259 } else { | |
260 ssl_pool = session->GetSocketPoolForSSLWithProxy(socket_pool_type, | |
261 *proxy_host_port); | |
262 } | |
263 | |
264 if (num_preconnect_streams) { | |
265 RequestSocketsForPool(ssl_pool, connection_group, ssl_params, | |
266 num_preconnect_streams, net_log); | |
267 return OK; | |
268 } | |
269 | |
270 return socket_handle->Init(connection_group, ssl_params, | |
271 request_priority, callback, ssl_pool, | |
272 net_log); | |
273 } | |
274 | |
275 // Finally, get the connection started. | |
276 | |
277 if (proxy_info.is_http() || proxy_info.is_https()) { | |
278 HttpProxyClientSocketPool* pool = | |
279 session->GetSocketPoolForHTTPProxy(socket_pool_type, *proxy_host_port); | |
280 if (num_preconnect_streams) { | |
281 RequestSocketsForPool(pool, connection_group, http_proxy_params, | |
282 num_preconnect_streams, net_log); | |
283 return OK; | |
284 } | |
285 | |
286 return socket_handle->Init(connection_group, http_proxy_params, | |
287 request_priority, callback, | |
288 pool, net_log); | |
289 } | |
290 | |
291 if (proxy_info.is_socks()) { | |
292 SOCKSClientSocketPool* pool = | |
293 session->GetSocketPoolForSOCKSProxy(socket_pool_type, *proxy_host_port); | |
294 if (num_preconnect_streams) { | |
295 RequestSocketsForPool(pool, connection_group, socks_params, | |
296 num_preconnect_streams, net_log); | |
297 return OK; | |
298 } | |
299 | |
300 return socket_handle->Init(connection_group, socks_params, | |
301 request_priority, callback, pool, | |
302 net_log); | |
303 } | |
304 | |
305 DCHECK(proxy_info.is_direct()); | |
306 scoped_refptr<TransportSocketParams> tcp_params = | |
307 new TransportSocketParams( | |
308 origin_host_port, | |
309 disable_resolver_cache, | |
310 ignore_limits, | |
311 resolution_callback, | |
312 TransportSocketParams::COMBINE_CONNECT_AND_WRITE_DEFAULT); | |
313 TransportClientSocketPool* pool = | |
314 session->GetTransportSocketPool(socket_pool_type); | |
315 if (num_preconnect_streams) { | |
316 RequestSocketsForPool(pool, connection_group, tcp_params, | |
317 num_preconnect_streams, net_log); | |
318 return OK; | |
319 } | |
320 | |
321 return socket_handle->Init(connection_group, tcp_params, | |
322 request_priority, callback, | |
323 pool, net_log); | |
324 } | |
325 | |
326 } // namespace | |
327 | |
328 ClientSocketPoolManager::ClientSocketPoolManager() {} | |
329 ClientSocketPoolManager::~ClientSocketPoolManager() {} | |
330 | |
331 // static | |
332 int ClientSocketPoolManager::max_sockets_per_pool( | |
333 HttpNetworkSession::SocketPoolType pool_type) { | |
334 DCHECK_LT(pool_type, HttpNetworkSession::NUM_SOCKET_POOL_TYPES); | |
335 return g_max_sockets_per_pool[pool_type]; | |
336 } | |
337 | |
338 // static | |
339 void ClientSocketPoolManager::set_max_sockets_per_pool( | |
340 HttpNetworkSession::SocketPoolType pool_type, | |
341 int socket_count) { | |
342 DCHECK_LT(0, socket_count); | |
343 DCHECK_GT(1000, socket_count); // Sanity check. | |
344 DCHECK_LT(pool_type, HttpNetworkSession::NUM_SOCKET_POOL_TYPES); | |
345 g_max_sockets_per_pool[pool_type] = socket_count; | |
346 DCHECK_GE(g_max_sockets_per_pool[pool_type], | |
347 g_max_sockets_per_group[pool_type]); | |
348 } | |
349 | |
350 // static | |
351 int ClientSocketPoolManager::max_sockets_per_group( | |
352 HttpNetworkSession::SocketPoolType pool_type) { | |
353 DCHECK_LT(pool_type, HttpNetworkSession::NUM_SOCKET_POOL_TYPES); | |
354 return g_max_sockets_per_group[pool_type]; | |
355 } | |
356 | |
357 // static | |
358 void ClientSocketPoolManager::set_max_sockets_per_group( | |
359 HttpNetworkSession::SocketPoolType pool_type, | |
360 int socket_count) { | |
361 DCHECK_LT(0, socket_count); | |
362 // The following is a sanity check... but we should NEVER be near this value. | |
363 DCHECK_GT(100, socket_count); | |
364 DCHECK_LT(pool_type, HttpNetworkSession::NUM_SOCKET_POOL_TYPES); | |
365 g_max_sockets_per_group[pool_type] = socket_count; | |
366 | |
367 DCHECK_GE(g_max_sockets_per_pool[pool_type], | |
368 g_max_sockets_per_group[pool_type]); | |
369 DCHECK_GE(g_max_sockets_per_proxy_server[pool_type], | |
370 g_max_sockets_per_group[pool_type]); | |
371 } | |
372 | |
373 // static | |
374 int ClientSocketPoolManager::max_sockets_per_proxy_server( | |
375 HttpNetworkSession::SocketPoolType pool_type) { | |
376 DCHECK_LT(pool_type, HttpNetworkSession::NUM_SOCKET_POOL_TYPES); | |
377 return g_max_sockets_per_proxy_server[pool_type]; | |
378 } | |
379 | |
380 // static | |
381 void ClientSocketPoolManager::set_max_sockets_per_proxy_server( | |
382 HttpNetworkSession::SocketPoolType pool_type, | |
383 int socket_count) { | |
384 DCHECK_LT(0, socket_count); | |
385 DCHECK_GT(100, socket_count); // Sanity check. | |
386 DCHECK_LT(pool_type, HttpNetworkSession::NUM_SOCKET_POOL_TYPES); | |
387 // Assert this case early on. The max number of sockets per group cannot | |
388 // exceed the max number of sockets per proxy server. | |
389 DCHECK_LE(g_max_sockets_per_group[pool_type], socket_count); | |
390 g_max_sockets_per_proxy_server[pool_type] = socket_count; | |
391 } | |
392 | |
393 int InitSocketHandleForHttpRequest( | |
394 const GURL& request_url, | |
395 const HttpRequestHeaders& request_extra_headers, | |
396 int request_load_flags, | |
397 RequestPriority request_priority, | |
398 HttpNetworkSession* session, | |
399 const ProxyInfo& proxy_info, | |
400 bool force_spdy_over_ssl, | |
401 bool want_spdy_over_npn, | |
402 const SSLConfig& ssl_config_for_origin, | |
403 const SSLConfig& ssl_config_for_proxy, | |
404 PrivacyMode privacy_mode, | |
405 const BoundNetLog& net_log, | |
406 ClientSocketHandle* socket_handle, | |
407 const OnHostResolutionCallback& resolution_callback, | |
408 const CompletionCallback& callback) { | |
409 DCHECK(socket_handle); | |
410 return InitSocketPoolHelper( | |
411 request_url, request_extra_headers, request_load_flags, request_priority, | |
412 session, proxy_info, force_spdy_over_ssl, want_spdy_over_npn, | |
413 ssl_config_for_origin, ssl_config_for_proxy, false, privacy_mode, net_log, | |
414 0, socket_handle, HttpNetworkSession::NORMAL_SOCKET_POOL, | |
415 resolution_callback, callback); | |
416 } | |
417 | |
418 int InitSocketHandleForWebSocketRequest( | |
419 const GURL& request_url, | |
420 const HttpRequestHeaders& request_extra_headers, | |
421 int request_load_flags, | |
422 RequestPriority request_priority, | |
423 HttpNetworkSession* session, | |
424 const ProxyInfo& proxy_info, | |
425 bool force_spdy_over_ssl, | |
426 bool want_spdy_over_npn, | |
427 const SSLConfig& ssl_config_for_origin, | |
428 const SSLConfig& ssl_config_for_proxy, | |
429 PrivacyMode privacy_mode, | |
430 const BoundNetLog& net_log, | |
431 ClientSocketHandle* socket_handle, | |
432 const OnHostResolutionCallback& resolution_callback, | |
433 const CompletionCallback& callback) { | |
434 DCHECK(socket_handle); | |
435 return InitSocketPoolHelper( | |
436 request_url, request_extra_headers, request_load_flags, request_priority, | |
437 session, proxy_info, force_spdy_over_ssl, want_spdy_over_npn, | |
438 ssl_config_for_origin, ssl_config_for_proxy, true, privacy_mode, net_log, | |
439 0, socket_handle, HttpNetworkSession::WEBSOCKET_SOCKET_POOL, | |
440 resolution_callback, callback); | |
441 } | |
442 | |
443 int InitSocketHandleForRawConnect( | |
444 const HostPortPair& host_port_pair, | |
445 HttpNetworkSession* session, | |
446 const ProxyInfo& proxy_info, | |
447 const SSLConfig& ssl_config_for_origin, | |
448 const SSLConfig& ssl_config_for_proxy, | |
449 PrivacyMode privacy_mode, | |
450 const BoundNetLog& net_log, | |
451 ClientSocketHandle* socket_handle, | |
452 const CompletionCallback& callback) { | |
453 DCHECK(socket_handle); | |
454 // Synthesize an HttpRequestInfo. | |
455 GURL request_url = GURL("http://" + host_port_pair.ToString()); | |
456 HttpRequestHeaders request_extra_headers; | |
457 int request_load_flags = 0; | |
458 RequestPriority request_priority = MEDIUM; | |
459 | |
460 return InitSocketPoolHelper( | |
461 request_url, request_extra_headers, request_load_flags, request_priority, | |
462 session, proxy_info, false, false, ssl_config_for_origin, | |
463 ssl_config_for_proxy, true, privacy_mode, net_log, 0, socket_handle, | |
464 HttpNetworkSession::NORMAL_SOCKET_POOL, OnHostResolutionCallback(), | |
465 callback); | |
466 } | |
467 | |
468 int InitSocketHandleForTlsConnect( | |
469 const HostPortPair& host_port_pair, | |
470 HttpNetworkSession* session, | |
471 const ProxyInfo& proxy_info, | |
472 const SSLConfig& ssl_config_for_origin, | |
473 const SSLConfig& ssl_config_for_proxy, | |
474 PrivacyMode privacy_mode, | |
475 const BoundNetLog& net_log, | |
476 ClientSocketHandle* socket_handle, | |
477 const CompletionCallback& callback) { | |
478 DCHECK(socket_handle); | |
479 // Synthesize an HttpRequestInfo. | |
480 GURL request_url = GURL("https://" + host_port_pair.ToString()); | |
481 HttpRequestHeaders request_extra_headers; | |
482 int request_load_flags = 0; | |
483 RequestPriority request_priority = MEDIUM; | |
484 | |
485 return InitSocketPoolHelper( | |
486 request_url, request_extra_headers, request_load_flags, request_priority, | |
487 session, proxy_info, false, false, ssl_config_for_origin, | |
488 ssl_config_for_proxy, true, privacy_mode, net_log, 0, socket_handle, | |
489 HttpNetworkSession::NORMAL_SOCKET_POOL, OnHostResolutionCallback(), | |
490 callback); | |
491 } | |
492 | |
493 int PreconnectSocketsForHttpRequest( | |
494 const GURL& request_url, | |
495 const HttpRequestHeaders& request_extra_headers, | |
496 int request_load_flags, | |
497 RequestPriority request_priority, | |
498 HttpNetworkSession* session, | |
499 const ProxyInfo& proxy_info, | |
500 bool force_spdy_over_ssl, | |
501 bool want_spdy_over_npn, | |
502 const SSLConfig& ssl_config_for_origin, | |
503 const SSLConfig& ssl_config_for_proxy, | |
504 PrivacyMode privacy_mode, | |
505 const BoundNetLog& net_log, | |
506 int num_preconnect_streams) { | |
507 return InitSocketPoolHelper( | |
508 request_url, request_extra_headers, request_load_flags, request_priority, | |
509 session, proxy_info, force_spdy_over_ssl, want_spdy_over_npn, | |
510 ssl_config_for_origin, ssl_config_for_proxy, false, privacy_mode, net_log, | |
511 num_preconnect_streams, NULL, HttpNetworkSession::NORMAL_SOCKET_POOL, | |
512 OnHostResolutionCallback(), CompletionCallback()); | |
513 } | |
514 | |
515 } // namespace net | |
OLD | NEW |