diff options
author | zea@chromium.org <zea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-14 06:05:29 +0000 |
---|---|---|
committer | zea@chromium.org <zea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-14 06:05:29 +0000 |
commit | 0e64bfe5f16b5e4a898cd957eba98a8430c8bca7 (patch) | |
tree | 870348c174065f43861c902899f6b96601fd7bd4 /google_apis/gcm | |
parent | ccd74c4eb9f984994731cb9a9047e1e2271bfa5d (diff) | |
download | chromium_src-0e64bfe5f16b5e4a898cd957eba98a8430c8bca7.zip chromium_src-0e64bfe5f16b5e4a898cd957eba98a8430c8bca7.tar.gz chromium_src-0e64bfe5f16b5e4a898cd957eba98a8430c8bca7.tar.bz2 |
[GCM] Add proxy resolution support.
GCM will use the proxy resolver associated with the current network session
to resolve proxies. There is no direct fallback at the moment, so if a proxy
resolution completes, but doesn't handle the data properly (because it's
intercepting/mangling the response), connections will fail.
Direct fallback, and attempting alternate ports, will be introduced in a later
patch.
BUG=351889
TBR=jianli@chromium.org
Review URL: https://codereview.chromium.org/197473008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@257023 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'google_apis/gcm')
-rw-r--r-- | google_apis/gcm/engine/connection_factory_impl.cc | 211 | ||||
-rw-r--r-- | google_apis/gcm/engine/connection_factory_impl.h | 16 | ||||
-rw-r--r-- | google_apis/gcm/engine/connection_handler_impl.cc | 12 |
3 files changed, 204 insertions, 35 deletions
diff --git a/google_apis/gcm/engine/connection_factory_impl.cc b/google_apis/gcm/engine/connection_factory_impl.cc index 3c2e412..8b0ed5c 100644 --- a/google_apis/gcm/engine/connection_factory_impl.cc +++ b/google_apis/gcm/engine/connection_factory_impl.cc @@ -48,13 +48,19 @@ ConnectionFactoryImpl::ConnectionFactoryImpl( : mcs_endpoint_(mcs_endpoint), backoff_policy_(backoff_policy), network_session_(network_session), - net_log_(net_log), + bound_net_log_( + net::BoundNetLog::Make(net_log, net::NetLog::SOURCE_SOCKET)), + pac_request_(NULL), connecting_(false), logging_in_(false), weak_ptr_factory_(this) { } ConnectionFactoryImpl::~ConnectionFactoryImpl() { + if (pac_request_) { + network_session_->proxy_service()->CancelPacRequest(pac_request_); + pac_request_ = NULL; + } } void ConnectionFactoryImpl::Initialize( @@ -128,12 +134,7 @@ void ConnectionFactoryImpl::SignalConnectionReset( // connection. } - if (connection_handler_) - connection_handler_->Reset(); - - if (socket_handle_.socket() && socket_handle_.socket()->IsConnected()) - socket_handle_.socket()->Disconnect(); - socket_handle_.Reset(); + CloseSocket(); if (logging_in_) { // Failures prior to login completion just reuse the existing backoff entry. @@ -147,7 +148,7 @@ void ConnectionFactoryImpl::SignalConnectionReset( backoff_entry_->InformOfRequest(false); } else { // We shouldn't be in backoff in thise case. - DCHECK(backoff_entry_->CanDiscard()); + DCHECK_EQ(0, backoff_entry_->failure_count()); } // At this point the last login time has been consumed or deemed irrelevant, @@ -187,25 +188,15 @@ void ConnectionFactoryImpl::ConnectImpl() { DCHECK(connecting_); DCHECK(!socket_handle_.socket()); - // TODO(zea): resolve proxies. - net::ProxyInfo proxy_info; - proxy_info.UseDirect(); - net::SSLConfig ssl_config; - network_session_->ssl_config_service()->GetSSLConfig(&ssl_config); - - int status = net::InitSocketHandleForTlsConnect( - net::HostPortPair::FromURL(mcs_endpoint_), - network_session_.get(), - proxy_info, - ssl_config, - ssl_config, - net::kPrivacyModeDisabled, - net::BoundNetLog::Make(net_log_, net::NetLog::SOURCE_SOCKET), - &socket_handle_, - base::Bind(&ConnectionFactoryImpl::OnConnectDone, - weak_ptr_factory_.GetWeakPtr())); + int status = network_session_->proxy_service()->ResolveProxy( + mcs_endpoint_, + &proxy_info_, + base::Bind(&ConnectionFactoryImpl::OnProxyResolveDone, + weak_ptr_factory_.GetWeakPtr()), + &pac_request_, + bound_net_log_); if (status != net::ERR_IO_PENDING) - OnConnectDone(status); + OnProxyResolveDone(status); } void ConnectionFactoryImpl::InitHandler() { @@ -229,16 +220,27 @@ base::TimeTicks ConnectionFactoryImpl::NowTicks() { } void ConnectionFactoryImpl::OnConnectDone(int result) { - UMA_HISTOGRAM_BOOLEAN("GCM.ConnectionSuccessRate", (result == net::OK)); - if (result != net::OK) { + // If the connection fails, try another proxy. + result = ReconsiderProxyAfterError(result); + // ReconsiderProxyAfterError either returns an error (in which case it is + // not reconsidering a proxy) or returns ERR_IO_PENDING if it is considering + // another proxy. + DCHECK_NE(result, net::OK); + if (result == net::ERR_IO_PENDING) + return; // Proxy reconsideration pending. Return. LOG(ERROR) << "Failed to connect to MCS endpoint with error " << result; + UMA_HISTOGRAM_BOOLEAN("GCM.ConnectionSuccessRate", false); + CloseSocket(); backoff_entry_->InformOfRequest(false); UMA_HISTOGRAM_SPARSE_SLOWLY("GCM.ConnectionFailureErrorCode", result); Connect(); return; } + UMA_HISTOGRAM_BOOLEAN("GCM.ConnectionSuccessRate", true); + ReportSuccessfulProxyConnection(); + connecting_ = false; logging_in_ = true; DVLOG(1) << "MCS endpoint socket connection success, starting login."; @@ -258,10 +260,163 @@ void ConnectionFactoryImpl::ConnectionHandlerCallback(int result) { // Handshake complete, reset backoff. If the login failed with an error, // the client should invoke SignalConnectionReset(LOGIN_FAILURE), which will // restore the previous backoff. + DVLOG(1) << "Handshake complete."; last_login_time_ = NowTicks(); previous_backoff_.swap(backoff_entry_); backoff_entry_->Reset(); logging_in_ = false; } +// This has largely been copied from +// HttpStreamFactoryImpl::Job::DoResolveProxyComplete. This should be +// refactored into some common place. +void ConnectionFactoryImpl::OnProxyResolveDone(int status) { + pac_request_ = NULL; + DVLOG(1) << "Proxy resolution status: " << status; + + DCHECK_NE(status, net::ERR_IO_PENDING); + if (status == net::OK) { + // Remove unsupported proxies from the list. + proxy_info_.RemoveProxiesWithoutScheme( + net::ProxyServer::SCHEME_DIRECT | + net::ProxyServer::SCHEME_HTTP | net::ProxyServer::SCHEME_HTTPS | + net::ProxyServer::SCHEME_SOCKS4 | net::ProxyServer::SCHEME_SOCKS5); + + if (proxy_info_.is_empty()) { + // No proxies/direct to choose from. This happens when we don't support + // any of the proxies in the returned list. + status = net::ERR_NO_SUPPORTED_PROXIES; + } + } + + if (status != net::OK) { + // Failed to resolve proxy. Retry later. + OnConnectDone(status); + return; + } + + DVLOG(1) << "Resolved proxy with PAC:" << proxy_info_.ToPacString(); + + net::SSLConfig ssl_config; + network_session_->ssl_config_service()->GetSSLConfig(&ssl_config); + status = net::InitSocketHandleForTlsConnect( + net::HostPortPair::FromURL(mcs_endpoint_), + network_session_.get(), + proxy_info_, + ssl_config, + ssl_config, + net::kPrivacyModeDisabled, + bound_net_log_, + &socket_handle_, + base::Bind(&ConnectionFactoryImpl::OnConnectDone, + weak_ptr_factory_.GetWeakPtr())); + if (status != net::ERR_IO_PENDING) + OnConnectDone(status); +} + +// This has largely been copied from +// HttpStreamFactoryImpl::Job::ReconsiderProxyAfterError. This should be +// refactored into some common place. +// This method reconsiders the proxy on certain errors. If it does reconsider +// a proxy it always returns ERR_IO_PENDING and posts a call to +// OnProxyResolveDone with the result of the reconsideration. +int ConnectionFactoryImpl::ReconsiderProxyAfterError(int error) { + DCHECK(!pac_request_); + DCHECK_NE(error, net::OK); + DCHECK_NE(error, net::ERR_IO_PENDING); + // A failure to resolve the hostname or any error related to establishing a + // TCP connection could be grounds for trying a new proxy configuration. + // + // Why do this when a hostname cannot be resolved? Some URLs only make sense + // to proxy servers. The hostname in those URLs might fail to resolve if we + // are still using a non-proxy config. We need to check if a proxy config + // now exists that corresponds to a proxy server that could load the URL. + // + switch (error) { + case net::ERR_PROXY_CONNECTION_FAILED: + case net::ERR_NAME_NOT_RESOLVED: + case net::ERR_INTERNET_DISCONNECTED: + case net::ERR_ADDRESS_UNREACHABLE: + case net::ERR_CONNECTION_CLOSED: + case net::ERR_CONNECTION_TIMED_OUT: + case net::ERR_CONNECTION_RESET: + case net::ERR_CONNECTION_REFUSED: + case net::ERR_CONNECTION_ABORTED: + case net::ERR_TIMED_OUT: + case net::ERR_TUNNEL_CONNECTION_FAILED: + case net::ERR_SOCKS_CONNECTION_FAILED: + // This can happen in the case of trying to talk to a proxy using SSL, and + // ending up talking to a captive portal that supports SSL instead. + case net::ERR_PROXY_CERTIFICATE_INVALID: + // This can happen when trying to talk SSL to a non-SSL server (Like a + // captive portal). + case net::ERR_SSL_PROTOCOL_ERROR: + break; + case net::ERR_SOCKS_CONNECTION_HOST_UNREACHABLE: + // Remap the SOCKS-specific "host unreachable" error to a more + // generic error code (this way consumers like the link doctor + // know to substitute their error page). + // + // Note that if the host resolving was done by the SOCKS5 proxy, we can't + // differentiate between a proxy-side "host not found" versus a proxy-side + // "address unreachable" error, and will report both of these failures as + // ERR_ADDRESS_UNREACHABLE. + return net::ERR_ADDRESS_UNREACHABLE; + default: + return error; + } + + net::SSLConfig ssl_config; + network_session_->ssl_config_service()->GetSSLConfig(&ssl_config); + if (proxy_info_.is_https() && ssl_config.send_client_cert) { + network_session_->ssl_client_auth_cache()->Remove( + proxy_info_.proxy_server().host_port_pair()); + } + + int status = network_session_->proxy_service()->ReconsiderProxyAfterError( + mcs_endpoint_, &proxy_info_, + base::Bind(&ConnectionFactoryImpl::OnProxyResolveDone, + weak_ptr_factory_.GetWeakPtr()), + &pac_request_, + bound_net_log_); + if (status == net::OK || status == net::ERR_IO_PENDING) { + CloseSocket(); + } else { + // If ReconsiderProxyAfterError() failed synchronously, it means + // there was nothing left to fall-back to, so fail the transaction + // with the last connection error we got. + status = error; + } + + // We either have new proxy info or there was an error in falling back. + // In both cases we want to post OnProxyResolveDone (in the error case + // we might still want to fall back a direct connection). + if (status != net::ERR_IO_PENDING) { + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&ConnectionFactoryImpl::OnProxyResolveDone, + weak_ptr_factory_.GetWeakPtr(), status)); + // Since we potentially have another try to go (trying the direct connect) + // set the return code code to ERR_IO_PENDING. + status = net::ERR_IO_PENDING; + } + return status; +} + +void ConnectionFactoryImpl::ReportSuccessfulProxyConnection() { + if (network_session_ && network_session_->proxy_service()) + network_session_->proxy_service()->ReportSuccess(proxy_info_); +} + +void ConnectionFactoryImpl::CloseSocket() { + // The connection handler needs to be reset, else it'll attempt to keep using + // the destroyed socket. + if (connection_handler_) + connection_handler_->Reset(); + + if (socket_handle_.socket() && socket_handle_.socket()->IsConnected()) + socket_handle_.socket()->Disconnect(); + socket_handle_.Reset(); +} + } // namespace gcm diff --git a/google_apis/gcm/engine/connection_factory_impl.h b/google_apis/gcm/engine/connection_factory_impl.h index 7326d79..967c6c1 100644 --- a/google_apis/gcm/engine/connection_factory_impl.h +++ b/google_apis/gcm/engine/connection_factory_impl.h @@ -12,6 +12,8 @@ #include "google_apis/gcm/protocol/mcs.pb.h" #include "net/base/backoff_entry.h" #include "net/base/network_change_notifier.h" +#include "net/proxy/proxy_info.h" +#include "net/proxy/proxy_service.h" #include "net/socket/client_socket_handle.h" #include "url/gurl.h" @@ -79,6 +81,14 @@ class GCM_EXPORT ConnectionFactoryImpl : void ConnectionHandlerCallback(int result); private: + // Proxy resolution and connection functions. + void OnProxyResolveDone(int status); + void OnProxyConnectDone(int status); + int ReconsiderProxyAfterError(int error); + void ReportSuccessfulProxyConnection(); + + void CloseSocket(); + // The MCS endpoint to make connections to. const GURL mcs_endpoint_; @@ -89,7 +99,11 @@ class GCM_EXPORT ConnectionFactoryImpl : // Network session for creating new connections. const scoped_refptr<net::HttpNetworkSession> network_session_; // Net log to use in connection attempts. - net::NetLog* const net_log_; + net::BoundNetLog bound_net_log_; + // The current PAC request, if one exists. Owned by the proxy service. + net::ProxyService::PacRequest* pac_request_; + // The current proxy info. + net::ProxyInfo proxy_info_; // The handle to the socket for the current connection, if one exists. net::ClientSocketHandle socket_handle_; // Current backoff entry. diff --git a/google_apis/gcm/engine/connection_handler_impl.cc b/google_apis/gcm/engine/connection_handler_impl.cc index be2c313b..513220d 100644 --- a/google_apis/gcm/engine/connection_handler_impl.cc +++ b/google_apis/gcm/engine/connection_handler_impl.cc @@ -357,18 +357,18 @@ void ConnectionHandlerImpl::OnGotMessageBytes() { input_stream_->GetState() != SocketInputStream::READY) { LOG(ERROR) << "Failed to extract protobuf bytes of type " << static_cast<unsigned int>(message_tag_); - protobuf.reset(); // Return a null pointer to denote an error. - read_callback_.Run(protobuf.Pass()); + // Reset the connection. + connection_callback_.Run(net::ERR_FAILED); return; } { CodedInputStream coded_input_stream(input_stream_.get()); if (!protobuf->ParsePartialFromCodedStream(&coded_input_stream)) { - NOTREACHED() << "Unable to parse GCM message of type " - << static_cast<unsigned int>(message_tag_); - protobuf.reset(); // Return a null pointer to denote an error. - read_callback_.Run(protobuf.Pass()); + LOG(ERROR) << "Unable to parse GCM message of type " + << static_cast<unsigned int>(message_tag_); + // Reset the connection. + connection_callback_.Run(net::ERR_FAILED); return; } } |