Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / google_apis / gcm / engine / connection_factory_impl.cc
index 3c2e412..cf90aed 100644 (file)
@@ -8,7 +8,9 @@
 #include "base/metrics/histogram.h"
 #include "base/metrics/sparse_histogram.h"
 #include "google_apis/gcm/engine/connection_handler_impl.h"
+#include "google_apis/gcm/monitoring/gcm_stats_recorder.h"
 #include "google_apis/gcm/protocol/mcs.pb.h"
+#include "net/base/load_flags.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_network_session.h"
 #include "net/http/http_request_headers.h"
@@ -41,20 +43,40 @@ bool ShouldRestorePreviousBackoff(const base::TimeTicks& login_time,
 }  // namespace
 
 ConnectionFactoryImpl::ConnectionFactoryImpl(
-    const GURL& mcs_endpoint,
+    const std::vector<GURL>& mcs_endpoints,
     const net::BackoffEntry::Policy& backoff_policy,
-    scoped_refptr<net::HttpNetworkSession> network_session,
-    net::NetLog* net_log)
-  : mcs_endpoint_(mcs_endpoint),
+    const scoped_refptr<net::HttpNetworkSession>& gcm_network_session,
+    const scoped_refptr<net::HttpNetworkSession>& http_network_session,
+    net::NetLog* net_log,
+    GCMStatsRecorder* recorder)
+  : mcs_endpoints_(mcs_endpoints),
+    next_endpoint_(0),
+    last_successful_endpoint_(0),
     backoff_policy_(backoff_policy),
-    network_session_(network_session),
-    net_log_(net_log),
+    gcm_network_session_(gcm_network_session),
+    http_network_session_(http_network_session),
+    bound_net_log_(
+        net::BoundNetLog::Make(net_log, net::NetLog::SOURCE_SOCKET)),
+    pac_request_(NULL),
     connecting_(false),
+    waiting_for_backoff_(false),
+    waiting_for_network_online_(false),
     logging_in_(false),
+    recorder_(recorder),
+    listener_(NULL),
     weak_ptr_factory_(this) {
+  DCHECK_GE(mcs_endpoints_.size(), 1U);
+  DCHECK(!http_network_session_.get() ||
+         (gcm_network_session_.get() != http_network_session_.get()));
 }
 
 ConnectionFactoryImpl::~ConnectionFactoryImpl() {
+  CloseSocket();
+  net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
+  if (pac_request_) {
+    gcm_network_session_->proxy_service()->CancelPacRequest(pac_request_);
+    pac_request_ = NULL;
+  }
 }
 
 void ConnectionFactoryImpl::Initialize(
@@ -67,15 +89,14 @@ void ConnectionFactoryImpl::Initialize(
   backoff_entry_ = CreateBackoffEntry(&backoff_policy_);
   request_builder_ = request_builder;
 
-  net::NetworkChangeNotifier::AddIPAddressObserver(this);
-  net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
-  connection_handler_.reset(
-      new ConnectionHandlerImpl(
-          base::TimeDelta::FromMilliseconds(kReadTimeoutMs),
-          read_callback,
-          write_callback,
-          base::Bind(&ConnectionFactoryImpl::ConnectionHandlerCallback,
-                     weak_ptr_factory_.GetWeakPtr())));
+  net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
+  waiting_for_network_online_ = net::NetworkChangeNotifier::IsOffline();
+  connection_handler_ = CreateConnectionHandler(
+      base::TimeDelta::FromMilliseconds(kReadTimeoutMs),
+      read_callback,
+      write_callback,
+      base::Bind(&ConnectionFactoryImpl::ConnectionHandlerCallback,
+                 weak_ptr_factory_.GetWeakPtr())).Pass();
 }
 
 ConnectionHandler* ConnectionFactoryImpl::GetConnectionHandler() const {
@@ -85,39 +106,77 @@ ConnectionHandler* ConnectionFactoryImpl::GetConnectionHandler() const {
 void ConnectionFactoryImpl::Connect() {
   DCHECK(connection_handler_);
 
-  connecting_ = true;
+  if (connecting_ || waiting_for_backoff_)
+    return;  // Connection attempt already in progress or pending.
+
+  if (IsEndpointReachable())
+    return;  // Already connected.
+
+  ConnectWithBackoff();
+}
+
+void ConnectionFactoryImpl::ConnectWithBackoff() {
+  // If a canary managed to connect while a backoff expiration was pending,
+  // just cleanup the internal state.
+  if (connecting_ || logging_in_ || IsEndpointReachable()) {
+    waiting_for_backoff_ = false;
+    return;
+  }
+
   if (backoff_entry_->ShouldRejectRequest()) {
     DVLOG(1) << "Delaying MCS endpoint connection for "
              << backoff_entry_->GetTimeUntilRelease().InMilliseconds()
              << " milliseconds.";
+    waiting_for_backoff_ = true;
+    recorder_->RecordConnectionDelayedDueToBackoff(
+        backoff_entry_->GetTimeUntilRelease().InMilliseconds());
     base::MessageLoop::current()->PostDelayedTask(
         FROM_HERE,
-        base::Bind(&ConnectionFactoryImpl::Connect,
+        base::Bind(&ConnectionFactoryImpl::ConnectWithBackoff,
                    weak_ptr_factory_.GetWeakPtr()),
         backoff_entry_->GetTimeUntilRelease());
     return;
   }
 
   DVLOG(1) << "Attempting connection to MCS endpoint.";
+  waiting_for_backoff_ = false;
   ConnectImpl();
 }
 
 bool ConnectionFactoryImpl::IsEndpointReachable() const {
-  return connection_handler_ &&
-      connection_handler_->CanSendMessage() &&
-      !connecting_;
+  return connection_handler_ && connection_handler_->CanSendMessage();
+}
+
+std::string ConnectionFactoryImpl::GetConnectionStateString() const {
+  if (IsEndpointReachable())
+    return "CONNECTED";
+  if (logging_in_)
+    return "LOGGING IN";
+  if (connecting_)
+    return "CONNECTING";
+  if (waiting_for_backoff_)
+    return "WAITING FOR BACKOFF";
+  if (waiting_for_network_online_)
+    return "WAITING FOR NETWORK CHANGE";
+  return "NOT CONNECTED";
 }
 
 void ConnectionFactoryImpl::SignalConnectionReset(
     ConnectionResetReason reason) {
   // A failure can trigger multiple resets, so no need to do anything if a
   // connection is already in progress.
-  if (connecting_)
+  if (connecting_) {
+    DVLOG(1) << "Connection in progress, ignoring reset.";
     return;
+  }
+
+  if (listener_)
+    listener_->OnDisconnected();
 
   UMA_HISTOGRAM_ENUMERATION("GCM.ConnectionResetReason",
                             reason,
                             CONNECTION_RESET_COUNT);
+  recorder_->RecordConnectionResetSignaled(reason);
   if (!last_login_time_.is_null()) {
     UMA_HISTOGRAM_CUSTOM_TIMES("GCM.ConnectionUpTime",
                                NowTicks() - last_login_time_,
@@ -128,12 +187,20 @@ void ConnectionFactoryImpl::SignalConnectionReset(
     // connection.
   }
 
-  if (connection_handler_)
-    connection_handler_->Reset();
+  CloseSocket();
+  DCHECK(!IsEndpointReachable());
 
-  if (socket_handle_.socket() && socket_handle_.socket()->IsConnected())
-    socket_handle_.socket()->Disconnect();
-  socket_handle_.Reset();
+  // TODO(zea): if the network is offline, don't attempt to connect.
+  // See crbug.com/396687
+
+  // Network changes get special treatment as they can trigger a one-off canary
+  // request that bypasses backoff (but does nothing if a connection is in
+  // progress). Other connection reset events can be ignored as a connection
+  // is already awaiting backoff expiration.
+  if (waiting_for_backoff_ && reason != NETWORK_CHANGE) {
+    DVLOG(1) << "Backoff expiration pending, ignoring reset.";
+    return;
+  }
 
   if (logging_in_) {
     // Failures prior to login completion just reuse the existing backoff entry.
@@ -145,9 +212,12 @@ void ConnectionFactoryImpl::SignalConnectionReset(
     // the backoff entry that was saved off at login completion time.
     backoff_entry_.swap(previous_backoff_);
     backoff_entry_->InformOfRequest(false);
+  } else if (reason == NETWORK_CHANGE) {
+    ConnectImpl();  // Canary attempts bypass backoff without resetting it.
+    return;
   } 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,
@@ -157,55 +227,76 @@ void ConnectionFactoryImpl::SignalConnectionReset(
   Connect();
 }
 
+void ConnectionFactoryImpl::SetConnectionListener(
+    ConnectionListener* listener) {
+  listener_ = listener;
+}
+
 base::TimeTicks ConnectionFactoryImpl::NextRetryAttempt() const {
   if (!backoff_entry_)
     return base::TimeTicks();
   return backoff_entry_->GetReleaseTime();
 }
 
-void ConnectionFactoryImpl::OnConnectionTypeChanged(
+void ConnectionFactoryImpl::OnNetworkChanged(
     net::NetworkChangeNotifier::ConnectionType type) {
-  if (type == net::NetworkChangeNotifier::CONNECTION_NONE)
+  if (type == net::NetworkChangeNotifier::CONNECTION_NONE) {
+    DVLOG(1) << "Network lost, resettion connection.";
+    waiting_for_network_online_ = true;
+
+    // Will do nothing due to |waiting_for_network_online_ == true|.
+    // TODO(zea): make the above statement actually true. See crbug.com/396687
+    SignalConnectionReset(NETWORK_CHANGE);
     return;
+  }
 
-  // TODO(zea): implement different backoff/retry policies based on connection
-  // type.
-  DVLOG(1) << "Connection type changed to " << type << ", resetting backoff.";
-  backoff_entry_->Reset();
-  // Connect(..) should be retrying with backoff already if a connection is
-  // necessary, so no need to call again.
+  DVLOG(1) << "Connection type changed to " << type << ", reconnecting.";
+  waiting_for_network_online_ = false;
+  SignalConnectionReset(NETWORK_CHANGE);
 }
 
-void ConnectionFactoryImpl::OnIPAddressChanged() {
-  DVLOG(1) << "IP Address changed, resetting backoff.";
-  backoff_entry_->Reset();
-  // Connect(..) should be retrying with backoff already if a connection is
-  // necessary, so no need to call again.
+GURL ConnectionFactoryImpl::GetCurrentEndpoint() const {
+  // Note that IsEndpointReachable() returns false anytime connecting_ is true,
+  // so while connecting this always uses |next_endpoint_|.
+  if (IsEndpointReachable())
+    return mcs_endpoints_[last_successful_endpoint_];
+  return mcs_endpoints_[next_endpoint_];
+}
+
+net::IPEndPoint ConnectionFactoryImpl::GetPeerIP() {
+  if (!socket_handle_.socket())
+    return net::IPEndPoint();
+
+  net::IPEndPoint ip_endpoint;
+  int result = socket_handle_.socket()->GetPeerAddress(&ip_endpoint);
+  if (result != net::OK)
+    return net::IPEndPoint();
+
+  return ip_endpoint;
 }
 
 void ConnectionFactoryImpl::ConnectImpl() {
-  DCHECK(connecting_);
+  DCHECK(!IsEndpointReachable());
   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);
+  // TODO(zea): if the network is offline, don't attempt to connect.
+  // See crbug.com/396687
 
-  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()));
+  connecting_ = true;
+  GURL current_endpoint = GetCurrentEndpoint();
+  recorder_->RecordConnectionInitiated(current_endpoint.host());
+  RebuildNetworkSessionAuthCache();
+  int status = gcm_network_session_->proxy_service()->ResolveProxy(
+      current_endpoint,
+      net::LOAD_NORMAL,
+      &proxy_info_,
+      base::Bind(&ConnectionFactoryImpl::OnProxyResolveDone,
+                 weak_ptr_factory_.GetWeakPtr()),
+      &pac_request_,
+      NULL,
+      bound_net_log_);
   if (status != net::ERR_IO_PENDING)
-    OnConnectDone(status);
+    OnProxyResolveDone(status);
 }
 
 void ConnectionFactoryImpl::InitHandler() {
@@ -224,21 +315,62 @@ scoped_ptr<net::BackoffEntry> ConnectionFactoryImpl::CreateBackoffEntry(
   return scoped_ptr<net::BackoffEntry>(new net::BackoffEntry(policy));
 }
 
+scoped_ptr<ConnectionHandler> ConnectionFactoryImpl::CreateConnectionHandler(
+    base::TimeDelta read_timeout,
+    const ConnectionHandler::ProtoReceivedCallback& read_callback,
+    const ConnectionHandler::ProtoSentCallback& write_callback,
+    const ConnectionHandler::ConnectionChangedCallback& connection_callback) {
+  return make_scoped_ptr<ConnectionHandler>(
+      new ConnectionHandlerImpl(read_timeout,
+                                read_callback,
+                                write_callback,
+                                connection_callback));
+}
+
 base::TimeTicks ConnectionFactoryImpl::NowTicks() {
   return base::TimeTicks::Now();
 }
 
 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);
+    recorder_->RecordConnectionFailure(result);
+    CloseSocket();
     backoff_entry_->InformOfRequest(false);
     UMA_HISTOGRAM_SPARSE_SLOWLY("GCM.ConnectionFailureErrorCode", result);
+
+    // If there are other endpoints available, use the next endpoint on the
+    // subsequent retry.
+    next_endpoint_++;
+    if (next_endpoint_ >= mcs_endpoints_.size())
+      next_endpoint_ = 0;
+    connecting_ = false;
     Connect();
     return;
   }
 
+  UMA_HISTOGRAM_BOOLEAN("GCM.ConnectionSuccessRate", true);
+  UMA_HISTOGRAM_COUNTS("GCM.ConnectionEndpoint", next_endpoint_);
+  UMA_HISTOGRAM_BOOLEAN("GCM.ConnectedViaProxy",
+                        !(proxy_info_.is_empty() || proxy_info_.is_direct()));
+  ReportSuccessfulProxyConnection();
+  recorder_->RecordConnectionSuccess();
+
+  // Reset the endpoint back to the default.
+  // TODO(zea): consider prioritizing endpoints more intelligently based on
+  // which ones succeed most for this client? Although that will affect
+  // measuring the success rate of the default endpoint vs fallback.
+  last_successful_endpoint_ = next_endpoint_;
+  next_endpoint_ = 0;
   connecting_ = false;
   logging_in_ = true;
   DVLOG(1) << "MCS endpoint socket connection success, starting login.";
@@ -258,10 +390,172 @@ 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;
+
+  if (listener_)
+    listener_->OnConnected(GetCurrentEndpoint(), GetPeerIP());
+}
+
+// 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;
+  gcm_network_session_->ssl_config_service()->GetSSLConfig(&ssl_config);
+  status = net::InitSocketHandleForTlsConnect(
+      net::HostPortPair::FromURL(GetCurrentEndpoint()),
+      gcm_network_session_.get(),
+      proxy_info_,
+      ssl_config,
+      ssl_config,
+      net::PRIVACY_MODE_DISABLED,
+      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;
+  gcm_network_session_->ssl_config_service()->GetSSLConfig(&ssl_config);
+  if (proxy_info_.is_https() && ssl_config.send_client_cert) {
+    gcm_network_session_->ssl_client_auth_cache()->Remove(
+        proxy_info_.proxy_server().host_port_pair());
+  }
+
+  int status = gcm_network_session_->proxy_service()->ReconsiderProxyAfterError(
+      GetCurrentEndpoint(), net::LOAD_NORMAL, error, &proxy_info_,
+      base::Bind(&ConnectionFactoryImpl::OnProxyResolveDone,
+                 weak_ptr_factory_.GetWeakPtr()),
+      &pac_request_,
+      NULL,
+      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;
+  }
+
+  // If there is new proxy info, post OnProxyResolveDone to retry it. Otherwise,
+  // if there was an error falling back, fail synchronously.
+  if (status == net::OK) {
+    base::MessageLoop::current()->PostTask(
+        FROM_HERE,
+        base::Bind(&ConnectionFactoryImpl::OnProxyResolveDone,
+                   weak_ptr_factory_.GetWeakPtr(), status));
+    status = net::ERR_IO_PENDING;
+  }
+  return status;
+}
+
+void ConnectionFactoryImpl::ReportSuccessfulProxyConnection() {
+  if (gcm_network_session_ && gcm_network_session_->proxy_service())
+    gcm_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();
+}
+
+void ConnectionFactoryImpl::RebuildNetworkSessionAuthCache() {
+  if (!http_network_session_ || !http_network_session_->http_auth_cache())
+    return;
+
+  gcm_network_session_->http_auth_cache()->UpdateAllFrom(
+      *http_network_session_->http_auth_cache());
 }
 
 }  // namespace gcm