OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "net/socket/ssl_client_socket_pool.h" | 5 #include "net/socket/ssl_client_socket_pool.h" |
6 | 6 |
7 #include "base/bind.h" | 7 #include "base/bind.h" |
8 #include "base/bind_helpers.h" | 8 #include "base/bind_helpers.h" |
9 #include "base/metrics/field_trial.h" | 9 #include "base/metrics/field_trial.h" |
10 #include "base/metrics/histogram.h" | 10 #include "base/metrics/histogram.h" |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
92 const scoped_refptr<HttpProxySocketParams>& | 92 const scoped_refptr<HttpProxySocketParams>& |
93 SSLSocketParams::GetHttpProxyConnectionParams() const { | 93 SSLSocketParams::GetHttpProxyConnectionParams() const { |
94 DCHECK_EQ(GetConnectionType(), HTTP_PROXY); | 94 DCHECK_EQ(GetConnectionType(), HTTP_PROXY); |
95 return http_proxy_params_; | 95 return http_proxy_params_; |
96 } | 96 } |
97 | 97 |
98 SSLConnectJobMessenger::SSLConnectJobMessenger() : weak_factory_(this) { | 98 SSLConnectJobMessenger::SSLConnectJobMessenger() : weak_factory_(this) { |
99 } | 99 } |
100 | 100 |
101 bool SSLConnectJobMessenger::CanProceed(SSLClientSocket* ssl_socket) { | 101 bool SSLConnectJobMessenger::CanProceed(SSLClientSocket* ssl_socket) { |
102 // If the session is in the session cache, or there are no connecting | 102 // If there are no connecting sockets allow the connection to proceed. |
103 // sockets allow the connection to proceed. | 103 return connecting_sockets_.empty(); |
104 if (ssl_socket->InSessionCache() || connecting_sockets_.empty()) { | |
105 return true; | |
106 } | |
107 return false; | |
108 } | 104 } |
109 | 105 |
110 void SSLConnectJobMessenger::MonitorConnectionResult( | 106 void SSLConnectJobMessenger::MonitorConnectionResult( |
111 SSLClientSocket* ssl_socket) { | 107 SSLClientSocket* ssl_socket) { |
112 connecting_sockets_.push_back(ssl_socket); | 108 connecting_sockets_.push_back(ssl_socket); |
113 ssl_socket->SetIsLeader(); | 109 ssl_socket->SetIsLeader(); |
114 // SSLConnectJobMessenger weak_ptrs are used here to ensure that | 110 // SSLConnectJobMessenger weak_ptrs are used here to ensure that |
115 // tasks posted with these callbacks are deregistered if the | 111 // tasks posted with these callbacks are deregistered if the |
116 // SSLConnectJobMessenger should become invalid before they're run. | 112 // SSLConnectJobMessenger should become invalid before they're run. |
117 ssl_socket->SetSocketFailureCallback(base::Bind( | 113 ssl_socket->SetSocketFailureCallback(base::Bind( |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
162 // Erase all deleted sockets and their callbacks, as well as the socket that | 158 // Erase all deleted sockets and their callbacks, as well as the socket that |
163 // will | 159 // will |
164 // be connected next. | 160 // be connected next. |
165 pending_sockets_and_callbacks_.erase(pending_sockets_and_callbacks_.begin(), | 161 pending_sockets_and_callbacks_.erase(pending_sockets_and_callbacks_.begin(), |
166 ++it); | 162 ++it); |
167 | 163 |
168 MonitorConnectionResult(ssl_socket); | 164 MonitorConnectionResult(ssl_socket); |
169 callback.Run(); | 165 callback.Run(); |
170 } | 166 } |
171 | 167 |
| 168 bool SSLConnectJobMessenger::IsNeeded() { |
| 169 return pending_sockets_and_callbacks_.empty() && connecting_sockets_.empty(); |
| 170 } |
| 171 |
172 void SSLConnectJobMessenger::RunAllJobs( | 172 void SSLConnectJobMessenger::RunAllJobs( |
173 std::vector<SocketAndCallback>& pending_sockets_and_callbacks) { | 173 std::vector<SocketAndCallback>& pending_sockets_and_callbacks) { |
174 scoped_refptr<base::SingleThreadTaskRunner> task_runner = | 174 scoped_refptr<base::SingleThreadTaskRunner> task_runner = |
175 base::ThreadTaskRunnerHandle::Get(); | 175 base::ThreadTaskRunnerHandle::Get(); |
176 for (std::vector<SocketAndCallback>::const_iterator it = | 176 for (std::vector<SocketAndCallback>::const_iterator it = |
177 pending_sockets_and_callbacks.begin(); | 177 pending_sockets_and_callbacks.begin(); |
178 it != pending_sockets_and_callbacks.end(); | 178 it != pending_sockets_and_callbacks.end(); |
179 ++it) | 179 ++it) |
180 task_runner->PostTask(FROM_HERE, it->callback); | 180 task_runner->PostTask(FROM_HERE, it->callback); |
181 } | 181 } |
182 | 182 |
183 // Timeout for the SSL handshake portion of the connect. | 183 // Timeout for the SSL handshake portion of the connect. |
184 static const int kSSLHandshakeTimeoutInSeconds = 30; | 184 static const int kSSLHandshakeTimeoutInSeconds = 30; |
185 | 185 |
186 SSLConnectJob::SSLConnectJob(const std::string& group_name, | 186 SSLConnectJob::SSLConnectJob(const std::string& group_name, |
187 RequestPriority priority, | 187 RequestPriority priority, |
188 const scoped_refptr<SSLSocketParams>& params, | 188 const scoped_refptr<SSLSocketParams>& params, |
189 const base::TimeDelta& timeout_duration, | 189 const base::TimeDelta& timeout_duration, |
190 TransportClientSocketPool* transport_pool, | 190 TransportClientSocketPool* transport_pool, |
191 SOCKSClientSocketPool* socks_pool, | 191 SOCKSClientSocketPool* socks_pool, |
192 HttpProxyClientSocketPool* http_proxy_pool, | 192 HttpProxyClientSocketPool* http_proxy_pool, |
193 ClientSocketFactory* client_socket_factory, | 193 ClientSocketFactory* client_socket_factory, |
194 HostResolver* host_resolver, | 194 HostResolver* host_resolver, |
195 const SSLClientSocketContext& context, | 195 const SSLClientSocketContext& context, |
196 SSLConnectJobMessenger* messenger, | 196 SSLConnectJobMessenger* messenger, |
| 197 AddMessengerCallback add_messenger_callback, |
| 198 DeleteMessengerCallback delete_messenger_callback, |
197 Delegate* delegate, | 199 Delegate* delegate, |
198 NetLog* net_log) | 200 NetLog* net_log) |
199 : ConnectJob(group_name, | 201 : ConnectJob(group_name, |
200 timeout_duration, | 202 timeout_duration, |
201 priority, | 203 priority, |
202 delegate, | 204 delegate, |
203 BoundNetLog::Make(net_log, NetLog::SOURCE_CONNECT_JOB)), | 205 BoundNetLog::Make(net_log, NetLog::SOURCE_CONNECT_JOB)), |
204 params_(params), | 206 params_(params), |
205 transport_pool_(transport_pool), | 207 transport_pool_(transport_pool), |
206 socks_pool_(socks_pool), | 208 socks_pool_(socks_pool), |
207 http_proxy_pool_(http_proxy_pool), | 209 http_proxy_pool_(http_proxy_pool), |
208 client_socket_factory_(client_socket_factory), | 210 client_socket_factory_(client_socket_factory), |
209 host_resolver_(host_resolver), | 211 host_resolver_(host_resolver), |
210 context_(context.cert_verifier, | 212 context_(context.cert_verifier, |
211 context.server_bound_cert_service, | 213 context.server_bound_cert_service, |
212 context.transport_security_state, | 214 context.transport_security_state, |
213 context.cert_transparency_verifier, | 215 context.cert_transparency_verifier, |
214 (params->privacy_mode() == PRIVACY_MODE_ENABLED | 216 (params->privacy_mode() == PRIVACY_MODE_ENABLED |
215 ? "pm/" + context.ssl_session_cache_shard | 217 ? "pm/" + context.ssl_session_cache_shard |
216 : context.ssl_session_cache_shard)), | 218 : context.ssl_session_cache_shard)), |
217 io_callback_( | 219 io_callback_( |
218 base::Bind(&SSLConnectJob::OnIOComplete, base::Unretained(this))), | 220 base::Bind(&SSLConnectJob::OnIOComplete, base::Unretained(this))), |
219 messenger_(messenger), | 221 messenger_(messenger), |
220 weak_factory_(this) { | 222 weak_factory_(this), |
| 223 add_messenger_callback_(add_messenger_callback), |
| 224 delete_messenger_callback_(delete_messenger_callback) { |
221 } | 225 } |
222 | 226 |
223 SSLConnectJob::~SSLConnectJob() {} | 227 SSLConnectJob::~SSLConnectJob() {} |
224 | 228 |
225 LoadState SSLConnectJob::GetLoadState() const { | 229 LoadState SSLConnectJob::GetLoadState() const { |
226 switch (next_state_) { | 230 switch (next_state_) { |
227 case STATE_TUNNEL_CONNECT_COMPLETE: | 231 case STATE_TUNNEL_CONNECT_COMPLETE: |
228 if (transport_socket_handle_->socket()) | 232 if (transport_socket_handle_->socket()) |
229 return LOAD_STATE_ESTABLISHING_PROXY_TUNNEL; | 233 return LOAD_STATE_ESTABLISHING_PROXY_TUNNEL; |
230 // else, fall through. | 234 // else, fall through. |
(...skipping 186 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
417 connect_timing_.connect_start = socket_connect_timing.connect_start; | 421 connect_timing_.connect_start = socket_connect_timing.connect_start; |
418 connect_timing_.dns_start = socket_connect_timing.dns_start; | 422 connect_timing_.dns_start = socket_connect_timing.dns_start; |
419 connect_timing_.dns_end = socket_connect_timing.dns_end; | 423 connect_timing_.dns_end = socket_connect_timing.dns_end; |
420 } | 424 } |
421 | 425 |
422 ssl_socket_ = client_socket_factory_->CreateSSLClientSocket( | 426 ssl_socket_ = client_socket_factory_->CreateSSLClientSocket( |
423 transport_socket_handle_.Pass(), | 427 transport_socket_handle_.Pass(), |
424 params_->host_and_port(), | 428 params_->host_and_port(), |
425 params_->ssl_config(), | 429 params_->ssl_config(), |
426 context_); | 430 context_); |
| 431 |
| 432 // Retrieve an SSLConnectJobMessenger for this connection if needed. |
| 433 if (!ssl_socket_->InSessionCache()) |
| 434 GetMessenger(ssl_socket_->GetSessionCacheKey()); |
| 435 |
427 return OK; | 436 return OK; |
428 } | 437 } |
429 | 438 |
430 int SSLConnectJob::DoCheckForResume() { | 439 int SSLConnectJob::DoCheckForResume() { |
431 next_state_ = STATE_SSL_CONNECT; | 440 next_state_ = STATE_SSL_CONNECT; |
| 441 if (ssl_socket_->InSessionCache()) |
| 442 return OK; |
| 443 |
432 if (messenger_->CanProceed(ssl_socket_.get())) { | 444 if (messenger_->CanProceed(ssl_socket_.get())) { |
433 if (ssl_socket_->InSessionCache()) | |
434 return OK; | |
435 messenger_->MonitorConnectionResult(ssl_socket_.get()); | 445 messenger_->MonitorConnectionResult(ssl_socket_.get()); |
436 return OK; | 446 return OK; |
437 } | 447 } |
| 448 |
438 messenger_->AddPendingSocket( | 449 messenger_->AddPendingSocket( |
439 ssl_socket_.get(), | 450 ssl_socket_.get(), |
440 base::Bind(&net::SSLConnectJob::ResumeSSLConnection, | 451 base::Bind(&net::SSLConnectJob::ResumeSSLConnection, |
441 weak_factory_.GetWeakPtr())); | 452 weak_factory_.GetWeakPtr())); |
442 return ERR_IO_PENDING; | 453 return ERR_IO_PENDING; |
443 } | 454 } |
444 | 455 |
445 int SSLConnectJob::DoSSLConnect() { | 456 int SSLConnectJob::DoSSLConnect() { |
446 next_state_ = STATE_SSL_CONNECT_COMPLETE; | 457 next_state_ = STATE_SSL_CONNECT_COMPLETE; |
447 | 458 |
448 connect_timing_.ssl_start = base::TimeTicks::Now(); | 459 connect_timing_.ssl_start = base::TimeTicks::Now(); |
449 | 460 |
450 return ssl_socket_->Connect(io_callback_); | 461 return ssl_socket_->Connect(io_callback_); |
451 } | 462 } |
452 | 463 |
453 int SSLConnectJob::DoSSLConnectComplete(int result) { | 464 int SSLConnectJob::DoSSLConnectComplete(int result) { |
454 connect_timing_.ssl_end = base::TimeTicks::Now(); | 465 connect_timing_.ssl_end = base::TimeTicks::Now(); |
455 | 466 |
456 if (result != OK && SSLClientSocket::GetEnableConnectJobWaiting()) | 467 if (SSLClientSocket::GetEnableConnectJobWaiting() && messenger_ != NULL) { |
457 messenger_->OnJobFailed(); | 468 if (result != OK) |
458 | 469 messenger_->OnJobFailed(); |
| 470 else { |
| 471 // If the connection was successful, determine if the job's messenger is |
| 472 // still needed. If not, remove the messenger. |
| 473 if (!messenger_->IsNeeded()) |
| 474 DeleteMessenger(ssl_socket_->GetSessionCacheKey()); |
| 475 } |
| 476 } |
459 SSLClientSocket::NextProtoStatus status = | 477 SSLClientSocket::NextProtoStatus status = |
460 SSLClientSocket::kNextProtoUnsupported; | 478 SSLClientSocket::kNextProtoUnsupported; |
461 std::string proto; | 479 std::string proto; |
462 std::string server_protos; | 480 std::string server_protos; |
463 // GetNextProto will fail and and trigger a NOTREACHED if we pass in a socket | 481 // GetNextProto will fail and and trigger a NOTREACHED if we pass in a socket |
464 // that hasn't had SSL_ImportFD called on it. If we get a certificate error | 482 // that hasn't had SSL_ImportFD called on it. If we get a certificate error |
465 // here, then we know that we called SSL_ImportFD. | 483 // here, then we know that we called SSL_ImportFD. |
466 if (result == OK || IsCertificateError(result)) | 484 if (result == OK || IsCertificateError(result)) |
467 status = ssl_socket_->GetNextProto(&proto, &server_protos); | 485 status = ssl_socket_->GetNextProto(&proto, &server_protos); |
468 | 486 |
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
576 } | 594 } |
577 | 595 |
578 return result; | 596 return result; |
579 } | 597 } |
580 | 598 |
581 void SSLConnectJob::ResumeSSLConnection() { | 599 void SSLConnectJob::ResumeSSLConnection() { |
582 DCHECK_EQ(next_state_, STATE_SSL_CONNECT); | 600 DCHECK_EQ(next_state_, STATE_SSL_CONNECT); |
583 OnIOComplete(OK); | 601 OnIOComplete(OK); |
584 } | 602 } |
585 | 603 |
| 604 void SSLConnectJob::GetMessenger(std::string cache_key) { |
| 605 messenger_ = add_messenger_callback_.Run(cache_key); |
| 606 } |
| 607 |
| 608 void SSLConnectJob::DeleteMessenger(std::string cache_key) { |
| 609 delete_messenger_callback_.Run(cache_key); |
| 610 messenger_ = NULL; |
| 611 } |
| 612 |
586 SSLConnectJob::State SSLConnectJob::GetInitialState( | 613 SSLConnectJob::State SSLConnectJob::GetInitialState( |
587 SSLSocketParams::ConnectionType connection_type) { | 614 SSLSocketParams::ConnectionType connection_type) { |
588 switch (connection_type) { | 615 switch (connection_type) { |
589 case SSLSocketParams::DIRECT: | 616 case SSLSocketParams::DIRECT: |
590 return STATE_TRANSPORT_CONNECT; | 617 return STATE_TRANSPORT_CONNECT; |
591 case SSLSocketParams::HTTP_PROXY: | 618 case SSLSocketParams::HTTP_PROXY: |
592 return STATE_TUNNEL_CONNECT; | 619 return STATE_TUNNEL_CONNECT; |
593 case SSLSocketParams::SOCKS_PROXY: | 620 case SSLSocketParams::SOCKS_PROXY: |
594 return STATE_SOCKS_CONNECT; | 621 return STATE_SOCKS_CONNECT; |
595 } | 622 } |
(...skipping 13 matching lines...) Expand all Loading... |
609 ClientSocketFactory* client_socket_factory, | 636 ClientSocketFactory* client_socket_factory, |
610 HostResolver* host_resolver, | 637 HostResolver* host_resolver, |
611 const SSLClientSocketContext& context, | 638 const SSLClientSocketContext& context, |
612 NetLog* net_log) | 639 NetLog* net_log) |
613 : transport_pool_(transport_pool), | 640 : transport_pool_(transport_pool), |
614 socks_pool_(socks_pool), | 641 socks_pool_(socks_pool), |
615 http_proxy_pool_(http_proxy_pool), | 642 http_proxy_pool_(http_proxy_pool), |
616 client_socket_factory_(client_socket_factory), | 643 client_socket_factory_(client_socket_factory), |
617 host_resolver_(host_resolver), | 644 host_resolver_(host_resolver), |
618 context_(context), | 645 context_(context), |
619 net_log_(net_log), | 646 net_log_(net_log) { |
620 messenger_map_(new MessengerMap) { | |
621 base::TimeDelta max_transport_timeout = base::TimeDelta(); | 647 base::TimeDelta max_transport_timeout = base::TimeDelta(); |
622 base::TimeDelta pool_timeout; | 648 base::TimeDelta pool_timeout; |
623 if (transport_pool_) | 649 if (transport_pool_) |
624 max_transport_timeout = transport_pool_->ConnectionTimeout(); | 650 max_transport_timeout = transport_pool_->ConnectionTimeout(); |
625 if (socks_pool_) { | 651 if (socks_pool_) { |
626 pool_timeout = socks_pool_->ConnectionTimeout(); | 652 pool_timeout = socks_pool_->ConnectionTimeout(); |
627 if (pool_timeout > max_transport_timeout) | 653 if (pool_timeout > max_transport_timeout) |
628 max_transport_timeout = pool_timeout; | 654 max_transport_timeout = pool_timeout; |
629 } | 655 } |
630 if (http_proxy_pool_) { | 656 if (http_proxy_pool_) { |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
682 base_.AddLowerLayeredPool(socks_pool_); | 708 base_.AddLowerLayeredPool(socks_pool_); |
683 if (http_proxy_pool_) | 709 if (http_proxy_pool_) |
684 base_.AddLowerLayeredPool(http_proxy_pool_); | 710 base_.AddLowerLayeredPool(http_proxy_pool_); |
685 } | 711 } |
686 | 712 |
687 SSLClientSocketPool::~SSLClientSocketPool() { | 713 SSLClientSocketPool::~SSLClientSocketPool() { |
688 if (ssl_config_service_.get()) | 714 if (ssl_config_service_.get()) |
689 ssl_config_service_->RemoveObserver(this); | 715 ssl_config_service_->RemoveObserver(this); |
690 } | 716 } |
691 | 717 |
692 scoped_ptr<ConnectJob> | 718 SSLConnectJobMessenger* |
693 SSLClientSocketPool::SSLConnectJobFactory::NewConnectJob( | 719 SSLClientSocketPool::SSLConnectJobFactory::AddSSLConnectJobMessenger( |
| 720 std::string cache_key) { |
| 721 MessengerMap::const_iterator it = messenger_map_.find(cache_key); |
| 722 if (it == messenger_map_.end()) { |
| 723 std::pair<MessengerMap::iterator, bool> iter = messenger_map_.insert( |
| 724 MessengerMap::value_type(cache_key, new SSLConnectJobMessenger())); |
| 725 it = iter.first; |
| 726 } |
| 727 return it->second; |
| 728 } |
| 729 |
| 730 void SSLClientSocketPool::SSLConnectJobFactory::DeleteSSLConnectJobMessenger( |
| 731 std::string cache_key) { |
| 732 MessengerMap::iterator it = messenger_map_.find(cache_key); |
| 733 delete it->second; |
| 734 messenger_map_.erase(it); |
| 735 } |
| 736 |
| 737 scoped_ptr<ConnectJob> SSLClientSocketPool::SSLConnectJobFactory::NewConnectJob( |
694 const std::string& group_name, | 738 const std::string& group_name, |
695 const PoolBase::Request& request, | 739 const PoolBase::Request& request, |
696 ConnectJob::Delegate* delegate) const { | 740 ConnectJob::Delegate* delegate) { |
697 SSLConnectJobMessenger* messenger; | 741 return scoped_ptr<ConnectJob>(new SSLConnectJob( |
698 std::string cache_key = SSLClientSocket::FormatSessionCacheKey( | 742 group_name, |
699 request.params()->host_and_port().ToString(), | 743 request.priority(), |
700 context_.ssl_session_cache_shard); | 744 request.params(), |
701 if (SSLClientSocket::GetEnableConnectJobWaiting()) { | 745 ConnectionTimeout(), |
702 MessengerMap::const_iterator it = messenger_map_->find(cache_key); | 746 transport_pool_, |
703 if (it == messenger_map_->end()) { | 747 socks_pool_, |
704 std::pair<MessengerMap::iterator, bool> iter = messenger_map_->insert( | 748 http_proxy_pool_, |
705 MessengerMap::value_type(cache_key, new SSLConnectJobMessenger())); | 749 client_socket_factory_, |
706 it = iter.first; | 750 host_resolver_, |
707 } | 751 context_, |
708 messenger = it->second; | 752 NULL, |
709 } else { | 753 base::Bind( |
710 messenger = NULL; | 754 &SSLClientSocketPool::SSLConnectJobFactory::AddSSLConnectJobMessenger, |
711 } | 755 base::Unretained(this)), |
712 | 756 base::Bind(&SSLClientSocketPool::SSLConnectJobFactory:: |
713 return scoped_ptr<ConnectJob>(new SSLConnectJob(group_name, | 757 DeleteSSLConnectJobMessenger, |
714 request.priority(), | 758 base::Unretained(this)), |
715 request.params(), | 759 delegate, |
716 ConnectionTimeout(), | 760 net_log_)); |
717 transport_pool_, | |
718 socks_pool_, | |
719 http_proxy_pool_, | |
720 client_socket_factory_, | |
721 host_resolver_, | |
722 context_, | |
723 messenger, | |
724 delegate, | |
725 net_log_)); | |
726 } | 761 } |
727 | 762 |
728 base::TimeDelta | 763 base::TimeDelta |
729 SSLClientSocketPool::SSLConnectJobFactory::ConnectionTimeout() const { | 764 SSLClientSocketPool::SSLConnectJobFactory::ConnectionTimeout() const { |
730 return timeout_; | 765 return timeout_; |
731 } | 766 } |
732 | 767 |
733 int SSLClientSocketPool::RequestSocket(const std::string& group_name, | 768 int SSLClientSocketPool::RequestSocket(const std::string& group_name, |
734 const void* socket_params, | 769 const void* socket_params, |
735 RequestPriority priority, | 770 RequestPriority priority, |
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
839 if (base_.CloseOneIdleSocket()) | 874 if (base_.CloseOneIdleSocket()) |
840 return true; | 875 return true; |
841 return base_.CloseOneIdleConnectionInHigherLayeredPool(); | 876 return base_.CloseOneIdleConnectionInHigherLayeredPool(); |
842 } | 877 } |
843 | 878 |
844 void SSLClientSocketPool::OnSSLConfigChanged() { | 879 void SSLClientSocketPool::OnSSLConfigChanged() { |
845 FlushWithError(ERR_NETWORK_CHANGED); | 880 FlushWithError(ERR_NETWORK_CHANGED); |
846 } | 881 } |
847 | 882 |
848 } // namespace net | 883 } // namespace net |
OLD | NEW |