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