blob: 2912057e4c159b4c562b3d0a2b9785a0524493d7 [file] [log] [blame]
derekjchow15b3f072015-06-03 00:01:431// Copyright 2015 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 "chromecast/net/connectivity_checker_impl.h"
6
7#include "base/command_line.h"
fdorayca3487e2016-06-03 13:06:478#include "base/location.h"
derekjchow15b3f072015-06-03 00:01:439#include "base/logging.h"
dcheng3c3c93d52016-04-08 05:12:3110#include "base/memory/ptr_util.h"
fdorayca3487e2016-06-03 13:06:4711#include "base/single_thread_task_runner.h"
12#include "base/threading/thread_task_runner_handle.h"
bcfaedc7082016-05-13 21:05:3713#include "base/values.h"
14#include "chromecast/base/metrics/cast_metrics_helper.h"
almasrymina21d4e08652016-09-27 22:59:1715#include "chromecast/chromecast_features.h"
derekjchow15b3f072015-06-03 00:01:4316#include "chromecast/net/net_switches.h"
17#include "net/base/request_priority.h"
18#include "net/http/http_response_headers.h"
19#include "net/http/http_response_info.h"
20#include "net/http/http_status_code.h"
21#include "net/proxy/proxy_config.h"
22#include "net/proxy/proxy_config_service_fixed.h"
wzhong2b152992015-09-01 00:52:1223#include "net/socket/ssl_client_socket.h"
derekjchow15b3f072015-06-03 00:01:4324#include "net/url_request/url_request_context.h"
25#include "net/url_request/url_request_context_builder.h"
26
27namespace chromecast {
28
29namespace {
30
almasrymina21d4e08652016-09-27 22:59:1731// How often connectivity checks are performed in seconds while not connected.
derekjchow15b3f072015-06-03 00:01:4332const unsigned int kConnectivityPeriodSeconds = 1;
33
almasrymina21d4e08652016-09-27 22:59:1734#if BUILDFLAG(IS_CAST_AUDIO_ONLY)
35// How often connectivity checks are performed in seconds while connected.
36const unsigned int kConnectivitySuccessPeriodSeconds = 60;
37#endif
38
derekjchow15b3f072015-06-03 00:01:4339// Number of consecutive connectivity check errors before status is changed
40// to offline.
41const unsigned int kNumErrorsToNotifyOffline = 3;
42
wzhongcb79e44a2015-06-10 01:23:5143// Request timeout value in seconds.
44const unsigned int kRequestTimeoutInSeconds = 3;
45
derekjchow15b3f072015-06-03 00:01:4346// Default url for connectivity checking.
47const char kDefaultConnectivityCheckUrl[] =
almasryminad92bcbf42016-04-29 01:03:5048 "https://connectivitycheck.gstatic.com/generate_204";
derekjchow15b3f072015-06-03 00:01:4349
gfhuanga2d008922016-01-09 00:43:5250// Delay notification of network change events to smooth out rapid flipping.
51// Histogram "Cast.Network.Down.Duration.In.Seconds" shows 40% of network
52// downtime is less than 3 seconds.
53const char kNetworkChangedDelayInSeconds = 3;
54
bcfaedc7082016-05-13 21:05:3755const char kMetricNameNetworkConnectivityCheckingErrorType[] =
56 "Network.ConnectivityChecking.ErrorType";
57
derekjchow15b3f072015-06-03 00:01:4358} // namespace
59
60ConnectivityCheckerImpl::ConnectivityCheckerImpl(
61 const scoped_refptr<base::SingleThreadTaskRunner>& task_runner)
62 : ConnectivityChecker(),
63 task_runner_(task_runner),
64 connected_(false),
gfhuanga2d008922016-01-09 00:43:5265 connection_type_(net::NetworkChangeNotifier::CONNECTION_NONE),
66 check_errors_(0),
67 network_changed_pending_(false) {
derekjchow15b3f072015-06-03 00:01:4368 DCHECK(task_runner_.get());
69 task_runner->PostTask(FROM_HERE,
70 base::Bind(&ConnectivityCheckerImpl::Initialize, this));
71}
72
73void ConnectivityCheckerImpl::Initialize() {
almasryminaf943b832016-06-17 18:44:0574 DCHECK(task_runner_->BelongsToCurrentThread());
derekjchow15b3f072015-06-03 00:01:4375 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
76 base::CommandLine::StringType check_url_str =
77 command_line->GetSwitchValueNative(switches::kConnectivityCheckUrl);
78 connectivity_check_url_.reset(new GURL(
79 check_url_str.empty() ? kDefaultConnectivityCheckUrl : check_url_str));
80
81 net::URLRequestContextBuilder builder;
ricea7344d1a2016-08-19 14:26:2482 builder.set_proxy_config_service(
83 base::MakeUnique<net::ProxyConfigServiceFixed>(
84 net::ProxyConfig::CreateDirect()));
derekjchow15b3f072015-06-03 00:01:4385 builder.DisableHttpCache();
dcheng298775f2015-12-29 03:10:5986 url_request_context_ = builder.Build();
derekjchow15b3f072015-06-03 00:01:4387
derekjchowd355fb52015-06-04 22:29:2588 net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
derekjchow15b3f072015-06-03 00:01:4389 task_runner_->PostTask(FROM_HERE,
90 base::Bind(&ConnectivityCheckerImpl::Check, this));
91}
92
93ConnectivityCheckerImpl::~ConnectivityCheckerImpl() {
94 DCHECK(task_runner_.get());
derekjchowd355fb52015-06-04 22:29:2595 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
derekjchow15b3f072015-06-03 00:01:4396 task_runner_->DeleteSoon(FROM_HERE, url_request_.release());
97 task_runner_->DeleteSoon(FROM_HERE, url_request_context_.release());
98}
99
100bool ConnectivityCheckerImpl::Connected() const {
almasryminaf943b832016-06-17 18:44:05101 base::AutoLock auto_lock(connected_lock_);
derekjchow15b3f072015-06-03 00:01:43102 return connected_;
103}
104
105void ConnectivityCheckerImpl::SetConnected(bool connected) {
almasryminaf943b832016-06-17 18:44:05106 DCHECK(task_runner_->BelongsToCurrentThread());
derekjchow15b3f072015-06-03 00:01:43107 if (connected_ == connected)
108 return;
almasryminaf943b832016-06-17 18:44:05109 {
110 base::AutoLock auto_lock(connected_lock_);
111 connected_ = connected;
112 }
derekjchowd9fddc22015-06-03 18:15:46113 Notify(connected);
derekjchow15b3f072015-06-03 00:01:43114 LOG(INFO) << "Global connection is: " << (connected ? "Up" : "Down");
115}
116
117void ConnectivityCheckerImpl::Check() {
almasryminaf943b832016-06-17 18:44:05118 task_runner_->PostTask(FROM_HERE,
119 base::Bind(&ConnectivityCheckerImpl::CheckInternal, this));
120}
121
122void ConnectivityCheckerImpl::CheckInternal() {
123 DCHECK(task_runner_->BelongsToCurrentThread());
derekjchow15b3f072015-06-03 00:01:43124 DCHECK(url_request_context_.get());
125
126 // Don't check connectivity if network is offline, because Internet could be
127 // accessible via netifs ignored.
128 if (net::NetworkChangeNotifier::IsOffline())
129 return;
130
131 // If url_request_ is non-null, there is already a check going on. Don't
132 // start another.
133 if (url_request_.get())
134 return;
135
136 VLOG(1) << "Connectivity check: url=" << *connectivity_check_url_;
137 url_request_ = url_request_context_->CreateRequest(
138 *connectivity_check_url_, net::MAXIMUM_PRIORITY, this);
139 url_request_->set_method("HEAD");
140 url_request_->Start();
wzhongcb79e44a2015-06-10 01:23:51141
142 timeout_.Reset(base::Bind(&ConnectivityCheckerImpl::OnUrlRequestTimeout,
143 this));
wzhong8ac32292015-09-15 21:45:59144 // Exponential backoff for timeout in 3, 6 and 12 sec.
145 const int timeout = kRequestTimeoutInSeconds
146 << (check_errors_ > 2 ? 2 : check_errors_);
fdorayca3487e2016-06-03 13:06:47147 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
148 FROM_HERE, timeout_.callback(), base::TimeDelta::FromSeconds(timeout));
derekjchow15b3f072015-06-03 00:01:43149}
150
derekjchowd355fb52015-06-04 22:29:25151void ConnectivityCheckerImpl::OnNetworkChanged(
derekjchow15b3f072015-06-03 00:01:43152 net::NetworkChangeNotifier::ConnectionType type) {
derekjchowd355fb52015-06-04 22:29:25153 VLOG(2) << "OnNetworkChanged " << type;
gfhuanga2d008922016-01-09 00:43:52154 connection_type_ = type;
155
156 if (network_changed_pending_)
157 return;
158 network_changed_pending_ = true;
fdorayca3487e2016-06-03 13:06:47159 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
gfhuanga2d008922016-01-09 00:43:52160 FROM_HERE,
161 base::Bind(&ConnectivityCheckerImpl::OnNetworkChangedInternal, this),
162 base::TimeDelta::FromSeconds(kNetworkChangedDelayInSeconds));
163}
164
165void ConnectivityCheckerImpl::OnNetworkChangedInternal() {
166 network_changed_pending_ = false;
derekjchowd355fb52015-06-04 22:29:25167 Cancel();
168
gfhuanga2d008922016-01-09 00:43:52169 if (connection_type_ == net::NetworkChangeNotifier::CONNECTION_NONE) {
derekjchow15b3f072015-06-03 00:01:43170 SetConnected(false);
derekjchowd355fb52015-06-04 22:29:25171 return;
172 }
derekjchow15b3f072015-06-03 00:01:43173
derekjchow15b3f072015-06-03 00:01:43174 Check();
175}
176
177void ConnectivityCheckerImpl::OnResponseStarted(net::URLRequest* request) {
almasryminaf943b832016-06-17 18:44:05178 DCHECK(task_runner_->BelongsToCurrentThread());
derekjchow15b3f072015-06-03 00:01:43179 int http_response_code =
180 (request->status().is_success() &&
derekjchowd9d1d002016-01-07 18:58:06181 request->response_info().headers.get() != nullptr)
derekjchow15b3f072015-06-03 00:01:43182 ? request->response_info().headers->response_code()
183 : net::HTTP_BAD_REQUEST;
184
185 // Clears resources.
derekjchowd9d1d002016-01-07 18:58:06186 url_request_.reset(nullptr); // URLRequest::Cancel() is called in destructor.
derekjchow15b3f072015-06-03 00:01:43187
188 if (http_response_code < 400) {
189 VLOG(1) << "Connectivity check succeeded";
190 check_errors_ = 0;
191 SetConnected(true);
almasrymina21d4e08652016-09-27 22:59:17192#if BUILDFLAG(IS_CAST_AUDIO_ONLY)
193 // Audio products do not have an idle screen that makes periodic network
194 // requests. Schedule another check for audio devices to make sure
195 // connectivity hasn't dropped.
196 task_runner_->PostDelayedTask(
197 FROM_HERE, base::Bind(&ConnectivityCheckerImpl::CheckInternal, this),
198 base::TimeDelta::FromSeconds(kConnectivitySuccessPeriodSeconds));
199#endif
derekjchowd9d1d002016-01-07 18:58:06200 timeout_.Cancel();
derekjchow15b3f072015-06-03 00:01:43201 return;
202 }
203 VLOG(1) << "Connectivity check failed: " << http_response_code;
bcfaedc7082016-05-13 21:05:37204 OnUrlRequestError(ErrorType::BAD_HTTP_STATUS);
derekjchowd9d1d002016-01-07 18:58:06205 timeout_.Cancel();
derekjchow15b3f072015-06-03 00:01:43206}
207
wzhongcb79e44a2015-06-10 01:23:51208void ConnectivityCheckerImpl::OnReadCompleted(net::URLRequest* request,
209 int bytes_read) {
210 NOTREACHED();
211}
212
derekjchow15b3f072015-06-03 00:01:43213void ConnectivityCheckerImpl::OnSSLCertificateError(
214 net::URLRequest* request,
215 const net::SSLInfo& ssl_info,
216 bool fatal) {
almasryminaf943b832016-06-17 18:44:05217 DCHECK(task_runner_->BelongsToCurrentThread());
derekjchow515644d2015-06-16 21:48:15218 LOG(ERROR) << "OnSSLCertificateError: cert_status=" << ssl_info.cert_status;
wzhong2b152992015-09-01 00:52:12219 net::SSLClientSocket::ClearSessionCache();
bcfaedc7082016-05-13 21:05:37220 OnUrlRequestError(ErrorType::SSL_CERTIFICATE_ERROR);
derekjchowd9d1d002016-01-07 18:58:06221 timeout_.Cancel();
derekjchow15b3f072015-06-03 00:01:43222}
223
bcfaedc7082016-05-13 21:05:37224void ConnectivityCheckerImpl::OnUrlRequestError(ErrorType type) {
almasryminaf943b832016-06-17 18:44:05225 DCHECK(task_runner_->BelongsToCurrentThread());
derekjchow15b3f072015-06-03 00:01:43226 ++check_errors_;
227 if (check_errors_ > kNumErrorsToNotifyOffline) {
bcfaedc7082016-05-13 21:05:37228 // Only record event on the connectivity transition.
229 if (connected_) {
230 metrics::CastMetricsHelper::GetInstance()->RecordEventWithValue(
231 kMetricNameNetworkConnectivityCheckingErrorType,
232 static_cast<int>(type));
233 }
derekjchow15b3f072015-06-03 00:01:43234 check_errors_ = kNumErrorsToNotifyOffline;
235 SetConnected(false);
236 }
derekjchowd9d1d002016-01-07 18:58:06237 url_request_.reset(nullptr);
derekjchow15b3f072015-06-03 00:01:43238 // Check again.
239 task_runner_->PostDelayedTask(
240 FROM_HERE, base::Bind(&ConnectivityCheckerImpl::Check, this),
241 base::TimeDelta::FromSeconds(kConnectivityPeriodSeconds));
242}
243
wzhongcb79e44a2015-06-10 01:23:51244void ConnectivityCheckerImpl::OnUrlRequestTimeout() {
almasryminaf943b832016-06-17 18:44:05245 DCHECK(task_runner_->BelongsToCurrentThread());
wzhongcb79e44a2015-06-10 01:23:51246 LOG(ERROR) << "time out";
bcfaedc7082016-05-13 21:05:37247 OnUrlRequestError(ErrorType::REQUEST_TIMEOUT);
derekjchow15b3f072015-06-03 00:01:43248}
249
250void ConnectivityCheckerImpl::Cancel() {
almasryminaf943b832016-06-17 18:44:05251 DCHECK(task_runner_->BelongsToCurrentThread());
wzhongcb79e44a2015-06-10 01:23:51252 if (!url_request_.get())
253 return;
254 VLOG(2) << "Cancel connectivity check in progress";
derekjchowd9d1d002016-01-07 18:58:06255 url_request_.reset(nullptr); // URLRequest::Cancel() is called in destructor.
wzhongcb79e44a2015-06-10 01:23:51256 timeout_.Cancel();
derekjchow15b3f072015-06-03 00:01:43257}
258
259} // namespace chromecast