Home | History | Annotate | Download | only in engine
      1 // Copyright (c) 2013 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 "google_apis/gcm/engine/connection_factory_impl.h"
      6 
      7 #include "base/message_loop/message_loop.h"
      8 #include "base/metrics/histogram.h"
      9 #include "base/metrics/sparse_histogram.h"
     10 #include "google_apis/gcm/engine/connection_handler_impl.h"
     11 #include "google_apis/gcm/monitoring/gcm_stats_recorder.h"
     12 #include "google_apis/gcm/protocol/mcs.pb.h"
     13 #include "net/base/net_errors.h"
     14 #include "net/http/http_network_session.h"
     15 #include "net/http/http_request_headers.h"
     16 #include "net/proxy/proxy_info.h"
     17 #include "net/socket/client_socket_handle.h"
     18 #include "net/socket/client_socket_pool_manager.h"
     19 #include "net/ssl/ssl_config_service.h"
     20 
     21 namespace gcm {
     22 
     23 namespace {
     24 
     25 // The amount of time a Socket read should wait before timing out.
     26 const int kReadTimeoutMs = 30000;  // 30 seconds.
     27 
     28 // If a connection is reset after succeeding within this window of time,
     29 // the previous backoff entry is restored (and the connection success is treated
     30 // as if it was transient).
     31 const int kConnectionResetWindowSecs = 10;  // 10 seconds.
     32 
     33 // Decides whether the last login was within kConnectionResetWindowSecs of now
     34 // or not.
     35 bool ShouldRestorePreviousBackoff(const base::TimeTicks& login_time,
     36                                   const base::TimeTicks& now_ticks) {
     37   return !login_time.is_null() &&
     38       now_ticks - login_time <=
     39           base::TimeDelta::FromSeconds(kConnectionResetWindowSecs);
     40 }
     41 
     42 }  // namespace
     43 
     44 ConnectionFactoryImpl::ConnectionFactoryImpl(
     45     const std::vector<GURL>& mcs_endpoints,
     46     const net::BackoffEntry::Policy& backoff_policy,
     47     scoped_refptr<net::HttpNetworkSession> network_session,
     48     net::NetLog* net_log,
     49     GCMStatsRecorder* recorder)
     50   : mcs_endpoints_(mcs_endpoints),
     51     next_endpoint_(0),
     52     last_successful_endpoint_(0),
     53     backoff_policy_(backoff_policy),
     54     network_session_(network_session),
     55     bound_net_log_(
     56         net::BoundNetLog::Make(net_log, net::NetLog::SOURCE_SOCKET)),
     57     pac_request_(NULL),
     58     connecting_(false),
     59     waiting_for_backoff_(false),
     60     waiting_for_network_online_(false),
     61     logging_in_(false),
     62     recorder_(recorder),
     63     listener_(NULL),
     64     weak_ptr_factory_(this) {
     65   DCHECK_GE(mcs_endpoints_.size(), 1U);
     66 }
     67 
     68 ConnectionFactoryImpl::~ConnectionFactoryImpl() {
     69   net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
     70   if (pac_request_) {
     71     network_session_->proxy_service()->CancelPacRequest(pac_request_);
     72     pac_request_ = NULL;
     73   }
     74 }
     75 
     76 void ConnectionFactoryImpl::Initialize(
     77     const BuildLoginRequestCallback& request_builder,
     78     const ConnectionHandler::ProtoReceivedCallback& read_callback,
     79     const ConnectionHandler::ProtoSentCallback& write_callback) {
     80   DCHECK(!connection_handler_);
     81 
     82   previous_backoff_ = CreateBackoffEntry(&backoff_policy_);
     83   backoff_entry_ = CreateBackoffEntry(&backoff_policy_);
     84   request_builder_ = request_builder;
     85 
     86   net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
     87   waiting_for_network_online_ = net::NetworkChangeNotifier::IsOffline();
     88   connection_handler_ = CreateConnectionHandler(
     89       base::TimeDelta::FromMilliseconds(kReadTimeoutMs),
     90       read_callback,
     91       write_callback,
     92       base::Bind(&ConnectionFactoryImpl::ConnectionHandlerCallback,
     93                  weak_ptr_factory_.GetWeakPtr())).Pass();
     94 }
     95 
     96 ConnectionHandler* ConnectionFactoryImpl::GetConnectionHandler() const {
     97   return connection_handler_.get();
     98 }
     99 
    100 void ConnectionFactoryImpl::Connect() {
    101   DCHECK(connection_handler_);
    102 
    103   if (connecting_ || waiting_for_backoff_)
    104     return;  // Connection attempt already in progress or pending.
    105 
    106   if (IsEndpointReachable())
    107     return;  // Already connected.
    108 
    109   ConnectWithBackoff();
    110 }
    111 
    112 void ConnectionFactoryImpl::ConnectWithBackoff() {
    113   // If a canary managed to connect while a backoff expiration was pending,
    114   // just cleanup the internal state.
    115   if (connecting_ || logging_in_ || IsEndpointReachable()) {
    116     waiting_for_backoff_ = false;
    117     return;
    118   }
    119 
    120   if (backoff_entry_->ShouldRejectRequest()) {
    121     DVLOG(1) << "Delaying MCS endpoint connection for "
    122              << backoff_entry_->GetTimeUntilRelease().InMilliseconds()
    123              << " milliseconds.";
    124     waiting_for_backoff_ = true;
    125     recorder_->RecordConnectionDelayedDueToBackoff(
    126         backoff_entry_->GetTimeUntilRelease().InMilliseconds());
    127     base::MessageLoop::current()->PostDelayedTask(
    128         FROM_HERE,
    129         base::Bind(&ConnectionFactoryImpl::ConnectWithBackoff,
    130                    weak_ptr_factory_.GetWeakPtr()),
    131         backoff_entry_->GetTimeUntilRelease());
    132     return;
    133   }
    134 
    135   DVLOG(1) << "Attempting connection to MCS endpoint.";
    136   waiting_for_backoff_ = false;
    137   ConnectImpl();
    138 }
    139 
    140 bool ConnectionFactoryImpl::IsEndpointReachable() const {
    141   return connection_handler_ && connection_handler_->CanSendMessage();
    142 }
    143 
    144 std::string ConnectionFactoryImpl::GetConnectionStateString() const {
    145   if (IsEndpointReachable())
    146     return "CONNECTED";
    147   if (logging_in_)
    148     return "LOGGING IN";
    149   if (connecting_)
    150     return "CONNECTING";
    151   if (waiting_for_backoff_)
    152     return "WAITING FOR BACKOFF";
    153   if (waiting_for_network_online_)
    154     return "WAITING FOR NETWORK CHANGE";
    155   return "NOT CONNECTED";
    156 }
    157 
    158 void ConnectionFactoryImpl::SignalConnectionReset(
    159     ConnectionResetReason reason) {
    160   // A failure can trigger multiple resets, so no need to do anything if a
    161   // connection is already in progress.
    162   if (connecting_) {
    163     DVLOG(1) << "Connection in progress, ignoring reset.";
    164     return;
    165   }
    166 
    167   if (listener_)
    168     listener_->OnDisconnected();
    169 
    170   UMA_HISTOGRAM_ENUMERATION("GCM.ConnectionResetReason",
    171                             reason,
    172                             CONNECTION_RESET_COUNT);
    173   recorder_->RecordConnectionResetSignaled(reason);
    174   if (!last_login_time_.is_null()) {
    175     UMA_HISTOGRAM_CUSTOM_TIMES("GCM.ConnectionUpTime",
    176                                NowTicks() - last_login_time_,
    177                                base::TimeDelta::FromSeconds(1),
    178                                base::TimeDelta::FromHours(24),
    179                                50);
    180     // |last_login_time_| will be reset below, before attempting the new
    181     // connection.
    182   }
    183 
    184   CloseSocket();
    185   DCHECK(!IsEndpointReachable());
    186 
    187   // TODO(zea): if the network is offline, don't attempt to connect.
    188   // See crbug.com/396687
    189 
    190   // Network changes get special treatment as they can trigger a one-off canary
    191   // request that bypasses backoff (but does nothing if a connection is in
    192   // progress). Other connection reset events can be ignored as a connection
    193   // is already awaiting backoff expiration.
    194   if (waiting_for_backoff_ && reason != NETWORK_CHANGE) {
    195     DVLOG(1) << "Backoff expiration pending, ignoring reset.";
    196     return;
    197   }
    198 
    199   if (logging_in_) {
    200     // Failures prior to login completion just reuse the existing backoff entry.
    201     logging_in_ = false;
    202     backoff_entry_->InformOfRequest(false);
    203   } else if (reason == LOGIN_FAILURE ||
    204              ShouldRestorePreviousBackoff(last_login_time_, NowTicks())) {
    205     // Failures due to login, or within the reset window of a login, restore
    206     // the backoff entry that was saved off at login completion time.
    207     backoff_entry_.swap(previous_backoff_);
    208     backoff_entry_->InformOfRequest(false);
    209   } else if (reason == NETWORK_CHANGE) {
    210     ConnectImpl();  // Canary attempts bypass backoff without resetting it.
    211     return;
    212   } else {
    213     // We shouldn't be in backoff in thise case.
    214     DCHECK_EQ(0, backoff_entry_->failure_count());
    215   }
    216 
    217   // At this point the last login time has been consumed or deemed irrelevant,
    218   // reset it.
    219   last_login_time_ = base::TimeTicks();
    220 
    221   Connect();
    222 }
    223 
    224 void ConnectionFactoryImpl::SetConnectionListener(
    225     ConnectionListener* listener) {
    226   listener_ = listener;
    227 }
    228 
    229 base::TimeTicks ConnectionFactoryImpl::NextRetryAttempt() const {
    230   if (!backoff_entry_)
    231     return base::TimeTicks();
    232   return backoff_entry_->GetReleaseTime();
    233 }
    234 
    235 void ConnectionFactoryImpl::OnNetworkChanged(
    236     net::NetworkChangeNotifier::ConnectionType type) {
    237   if (type == net::NetworkChangeNotifier::CONNECTION_NONE) {
    238     DVLOG(1) << "Network lost, resettion connection.";
    239     waiting_for_network_online_ = true;
    240 
    241     // Will do nothing due to |waiting_for_network_online_ == true|.
    242     // TODO(zea): make the above statement actually true. See crbug.com/396687
    243     SignalConnectionReset(NETWORK_CHANGE);
    244     return;
    245   }
    246 
    247   DVLOG(1) << "Connection type changed to " << type << ", reconnecting.";
    248   waiting_for_network_online_ = false;
    249   SignalConnectionReset(NETWORK_CHANGE);
    250 }
    251 
    252 GURL ConnectionFactoryImpl::GetCurrentEndpoint() const {
    253   // Note that IsEndpointReachable() returns false anytime connecting_ is true,
    254   // so while connecting this always uses |next_endpoint_|.
    255   if (IsEndpointReachable())
    256     return mcs_endpoints_[last_successful_endpoint_];
    257   return mcs_endpoints_[next_endpoint_];
    258 }
    259 
    260 net::IPEndPoint ConnectionFactoryImpl::GetPeerIP() {
    261   if (!socket_handle_.socket())
    262     return net::IPEndPoint();
    263 
    264   net::IPEndPoint ip_endpoint;
    265   int result = socket_handle_.socket()->GetPeerAddress(&ip_endpoint);
    266   if (result != net::OK)
    267     return net::IPEndPoint();
    268 
    269   return ip_endpoint;
    270 }
    271 
    272 void ConnectionFactoryImpl::ConnectImpl() {
    273   DCHECK(!IsEndpointReachable());
    274   DCHECK(!socket_handle_.socket());
    275 
    276   // TODO(zea): if the network is offline, don't attempt to connect.
    277   // See crbug.com/396687
    278 
    279   connecting_ = true;
    280   GURL current_endpoint = GetCurrentEndpoint();
    281   recorder_->RecordConnectionInitiated(current_endpoint.host());
    282   int status = network_session_->proxy_service()->ResolveProxy(
    283       current_endpoint,
    284       &proxy_info_,
    285       base::Bind(&ConnectionFactoryImpl::OnProxyResolveDone,
    286                  weak_ptr_factory_.GetWeakPtr()),
    287       &pac_request_,
    288       bound_net_log_);
    289   if (status != net::ERR_IO_PENDING)
    290     OnProxyResolveDone(status);
    291 }
    292 
    293 void ConnectionFactoryImpl::InitHandler() {
    294   // May be null in tests.
    295   mcs_proto::LoginRequest login_request;
    296   if (!request_builder_.is_null()) {
    297     request_builder_.Run(&login_request);
    298     DCHECK(login_request.IsInitialized());
    299   }
    300 
    301   connection_handler_->Init(login_request, socket_handle_.socket());
    302 }
    303 
    304 scoped_ptr<net::BackoffEntry> ConnectionFactoryImpl::CreateBackoffEntry(
    305     const net::BackoffEntry::Policy* const policy) {
    306   return scoped_ptr<net::BackoffEntry>(new net::BackoffEntry(policy));
    307 }
    308 
    309 scoped_ptr<ConnectionHandler> ConnectionFactoryImpl::CreateConnectionHandler(
    310     base::TimeDelta read_timeout,
    311     const ConnectionHandler::ProtoReceivedCallback& read_callback,
    312     const ConnectionHandler::ProtoSentCallback& write_callback,
    313     const ConnectionHandler::ConnectionChangedCallback& connection_callback) {
    314   return make_scoped_ptr<ConnectionHandler>(
    315       new ConnectionHandlerImpl(read_timeout,
    316                                 read_callback,
    317                                 write_callback,
    318                                 connection_callback));
    319 }
    320 
    321 base::TimeTicks ConnectionFactoryImpl::NowTicks() {
    322   return base::TimeTicks::Now();
    323 }
    324 
    325 void ConnectionFactoryImpl::OnConnectDone(int result) {
    326   if (result != net::OK) {
    327     // If the connection fails, try another proxy.
    328     result = ReconsiderProxyAfterError(result);
    329     // ReconsiderProxyAfterError either returns an error (in which case it is
    330     // not reconsidering a proxy) or returns ERR_IO_PENDING if it is considering
    331     // another proxy.
    332     DCHECK_NE(result, net::OK);
    333     if (result == net::ERR_IO_PENDING)
    334       return;  // Proxy reconsideration pending. Return.
    335     LOG(ERROR) << "Failed to connect to MCS endpoint with error " << result;
    336     UMA_HISTOGRAM_BOOLEAN("GCM.ConnectionSuccessRate", false);
    337     recorder_->RecordConnectionFailure(result);
    338     CloseSocket();
    339     backoff_entry_->InformOfRequest(false);
    340     UMA_HISTOGRAM_SPARSE_SLOWLY("GCM.ConnectionFailureErrorCode", result);
    341 
    342     // If there are other endpoints available, use the next endpoint on the
    343     // subsequent retry.
    344     next_endpoint_++;
    345     if (next_endpoint_ >= mcs_endpoints_.size())
    346       next_endpoint_ = 0;
    347     connecting_ = false;
    348     Connect();
    349     return;
    350   }
    351 
    352   UMA_HISTOGRAM_BOOLEAN("GCM.ConnectionSuccessRate", true);
    353   UMA_HISTOGRAM_COUNTS("GCM.ConnectionEndpoint", next_endpoint_);
    354   UMA_HISTOGRAM_BOOLEAN("GCM.ConnectedViaProxy",
    355                         !(proxy_info_.is_empty() || proxy_info_.is_direct()));
    356   ReportSuccessfulProxyConnection();
    357   recorder_->RecordConnectionSuccess();
    358 
    359   // Reset the endpoint back to the default.
    360   // TODO(zea): consider prioritizing endpoints more intelligently based on
    361   // which ones succeed most for this client? Although that will affect
    362   // measuring the success rate of the default endpoint vs fallback.
    363   last_successful_endpoint_ = next_endpoint_;
    364   next_endpoint_ = 0;
    365   connecting_ = false;
    366   logging_in_ = true;
    367   DVLOG(1) << "MCS endpoint socket connection success, starting login.";
    368   InitHandler();
    369 }
    370 
    371 void ConnectionFactoryImpl::ConnectionHandlerCallback(int result) {
    372   DCHECK(!connecting_);
    373   if (result != net::OK) {
    374     // TODO(zea): Consider how to handle errors that may require some sort of
    375     // user intervention (login page, etc.).
    376     UMA_HISTOGRAM_SPARSE_SLOWLY("GCM.ConnectionDisconnectErrorCode", result);
    377     SignalConnectionReset(SOCKET_FAILURE);
    378     return;
    379   }
    380 
    381   // Handshake complete, reset backoff. If the login failed with an error,
    382   // the client should invoke SignalConnectionReset(LOGIN_FAILURE), which will
    383   // restore the previous backoff.
    384   DVLOG(1) << "Handshake complete.";
    385   last_login_time_ = NowTicks();
    386   previous_backoff_.swap(backoff_entry_);
    387   backoff_entry_->Reset();
    388   logging_in_ = false;
    389 
    390   if (listener_)
    391     listener_->OnConnected(GetCurrentEndpoint(), GetPeerIP());
    392 }
    393 
    394 // This has largely been copied from
    395 // HttpStreamFactoryImpl::Job::DoResolveProxyComplete. This should be
    396 // refactored into some common place.
    397 void ConnectionFactoryImpl::OnProxyResolveDone(int status) {
    398   pac_request_ = NULL;
    399   DVLOG(1) << "Proxy resolution status: " << status;
    400 
    401   DCHECK_NE(status, net::ERR_IO_PENDING);
    402   if (status == net::OK) {
    403     // Remove unsupported proxies from the list.
    404     proxy_info_.RemoveProxiesWithoutScheme(
    405         net::ProxyServer::SCHEME_DIRECT |
    406         net::ProxyServer::SCHEME_HTTP | net::ProxyServer::SCHEME_HTTPS |
    407         net::ProxyServer::SCHEME_SOCKS4 | net::ProxyServer::SCHEME_SOCKS5);
    408 
    409     if (proxy_info_.is_empty()) {
    410       // No proxies/direct to choose from. This happens when we don't support
    411       // any of the proxies in the returned list.
    412       status = net::ERR_NO_SUPPORTED_PROXIES;
    413     }
    414   }
    415 
    416   if (status != net::OK) {
    417     // Failed to resolve proxy. Retry later.
    418     OnConnectDone(status);
    419     return;
    420   }
    421 
    422   DVLOG(1) << "Resolved proxy with PAC:" << proxy_info_.ToPacString();
    423 
    424   net::SSLConfig ssl_config;
    425   network_session_->ssl_config_service()->GetSSLConfig(&ssl_config);
    426   status = net::InitSocketHandleForTlsConnect(
    427       net::HostPortPair::FromURL(GetCurrentEndpoint()),
    428       network_session_.get(),
    429       proxy_info_,
    430       ssl_config,
    431       ssl_config,
    432       net::PRIVACY_MODE_DISABLED,
    433       bound_net_log_,
    434       &socket_handle_,
    435       base::Bind(&ConnectionFactoryImpl::OnConnectDone,
    436                  weak_ptr_factory_.GetWeakPtr()));
    437   if (status != net::ERR_IO_PENDING)
    438     OnConnectDone(status);
    439 }
    440 
    441 // This has largely been copied from
    442 // HttpStreamFactoryImpl::Job::ReconsiderProxyAfterError. This should be
    443 // refactored into some common place.
    444 // This method reconsiders the proxy on certain errors. If it does reconsider
    445 // a proxy it always returns ERR_IO_PENDING and posts a call to
    446 // OnProxyResolveDone with the result of the reconsideration.
    447 int ConnectionFactoryImpl::ReconsiderProxyAfterError(int error) {
    448   DCHECK(!pac_request_);
    449   DCHECK_NE(error, net::OK);
    450   DCHECK_NE(error, net::ERR_IO_PENDING);
    451   // A failure to resolve the hostname or any error related to establishing a
    452   // TCP connection could be grounds for trying a new proxy configuration.
    453   //
    454   // Why do this when a hostname cannot be resolved?  Some URLs only make sense
    455   // to proxy servers.  The hostname in those URLs might fail to resolve if we
    456   // are still using a non-proxy config.  We need to check if a proxy config
    457   // now exists that corresponds to a proxy server that could load the URL.
    458   //
    459   switch (error) {
    460     case net::ERR_PROXY_CONNECTION_FAILED:
    461     case net::ERR_NAME_NOT_RESOLVED:
    462     case net::ERR_INTERNET_DISCONNECTED:
    463     case net::ERR_ADDRESS_UNREACHABLE:
    464     case net::ERR_CONNECTION_CLOSED:
    465     case net::ERR_CONNECTION_TIMED_OUT:
    466     case net::ERR_CONNECTION_RESET:
    467     case net::ERR_CONNECTION_REFUSED:
    468     case net::ERR_CONNECTION_ABORTED:
    469     case net::ERR_TIMED_OUT:
    470     case net::ERR_TUNNEL_CONNECTION_FAILED:
    471     case net::ERR_SOCKS_CONNECTION_FAILED:
    472     // This can happen in the case of trying to talk to a proxy using SSL, and
    473     // ending up talking to a captive portal that supports SSL instead.
    474     case net::ERR_PROXY_CERTIFICATE_INVALID:
    475     // This can happen when trying to talk SSL to a non-SSL server (Like a
    476     // captive portal).
    477     case net::ERR_SSL_PROTOCOL_ERROR:
    478       break;
    479     case net::ERR_SOCKS_CONNECTION_HOST_UNREACHABLE:
    480       // Remap the SOCKS-specific "host unreachable" error to a more
    481       // generic error code (this way consumers like the link doctor
    482       // know to substitute their error page).
    483       //
    484       // Note that if the host resolving was done by the SOCKS5 proxy, we can't
    485       // differentiate between a proxy-side "host not found" versus a proxy-side
    486       // "address unreachable" error, and will report both of these failures as
    487       // ERR_ADDRESS_UNREACHABLE.
    488       return net::ERR_ADDRESS_UNREACHABLE;
    489     default:
    490       return error;
    491   }
    492 
    493   net::SSLConfig ssl_config;
    494   network_session_->ssl_config_service()->GetSSLConfig(&ssl_config);
    495   if (proxy_info_.is_https() && ssl_config.send_client_cert) {
    496     network_session_->ssl_client_auth_cache()->Remove(
    497         proxy_info_.proxy_server().host_port_pair());
    498   }
    499 
    500   int status = network_session_->proxy_service()->ReconsiderProxyAfterError(
    501       GetCurrentEndpoint(), error, &proxy_info_,
    502       base::Bind(&ConnectionFactoryImpl::OnProxyResolveDone,
    503                  weak_ptr_factory_.GetWeakPtr()),
    504       &pac_request_,
    505       bound_net_log_);
    506   if (status == net::OK || status == net::ERR_IO_PENDING) {
    507     CloseSocket();
    508   } else {
    509     // If ReconsiderProxyAfterError() failed synchronously, it means
    510     // there was nothing left to fall-back to, so fail the transaction
    511     // with the last connection error we got.
    512     status = error;
    513   }
    514 
    515   // If there is new proxy info, post OnProxyResolveDone to retry it. Otherwise,
    516   // if there was an error falling back, fail synchronously.
    517   if (status == net::OK) {
    518     base::MessageLoop::current()->PostTask(
    519         FROM_HERE,
    520         base::Bind(&ConnectionFactoryImpl::OnProxyResolveDone,
    521                    weak_ptr_factory_.GetWeakPtr(), status));
    522     status = net::ERR_IO_PENDING;
    523   }
    524   return status;
    525 }
    526 
    527 void ConnectionFactoryImpl::ReportSuccessfulProxyConnection() {
    528   if (network_session_ && network_session_->proxy_service())
    529     network_session_->proxy_service()->ReportSuccess(proxy_info_);
    530 }
    531 
    532 void ConnectionFactoryImpl::CloseSocket() {
    533   // The connection handler needs to be reset, else it'll attempt to keep using
    534   // the destroyed socket.
    535   if (connection_handler_)
    536     connection_handler_->Reset();
    537 
    538   if (socket_handle_.socket() && socket_handle_.socket()->IsConnected())
    539     socket_handle_.socket()->Disconnect();
    540   socket_handle_.Reset();
    541 }
    542 
    543 }  // namespace gcm
    544