1 // Copyright 2019 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 "remoting/host/heartbeat_sender.h"
6
7 #include <math.h>
8
9 #include <cstdint>
10 #include <utility>
11
12 #include "base/bind.h"
13 #include "base/rand_util.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/stringize_macros.h"
16 #include "base/system/sys_info.h"
17 #include "base/time/time.h"
18 #include "build/build_config.h"
19 #include "net/base/network_interfaces.h"
20 #include "net/traffic_annotation/network_traffic_annotation.h"
21 #include "remoting/base/constants.h"
22 #include "remoting/base/logging.h"
23 #include "remoting/base/protobuf_http_client.h"
24 #include "remoting/base/protobuf_http_request.h"
25 #include "remoting/base/protobuf_http_request_config.h"
26 #include "remoting/base/protobuf_http_status.h"
27 #include "remoting/base/service_urls.h"
28 #include "remoting/host/host_config.h"
29 #include "remoting/host/host_details.h"
30 #include "remoting/host/server_log_entry_host.h"
31 #include "remoting/signaling/ftl_signal_strategy.h"
32 #include "remoting/signaling/signaling_address.h"
33 #include "services/network/public/cpp/shared_url_loader_factory.h"
34
35 #if defined(OS_WIN)
36 #include "base/strings/utf_string_conversions.h"
37 #endif
38
39 namespace remoting {
40
41 namespace {
42
43 constexpr char kHeartbeatPath[] = "/v1/directory:heartbeat";
44
45 constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
46 net::DefineNetworkTrafficAnnotation("heartbeat_sender",
47 R"(
48 semantics {
49 sender: "Chrome Remote Desktop"
50 description:
51 "Sends heartbeat data to the Chrome Remote Desktop backend so that "
52 "the client knows about the presence of the host."
53 trigger:
54 "Starting a Chrome Remote Desktop host."
55 data:
56 "Chrome Remote Desktop Host ID and some non-PII information about "
57 "the host system such as the Chrome Remote Desktop version and the "
58 "OS version."
59 destination: OTHER
60 destination_other: "Chrome Remote Desktop directory service"
61 }
62 policy {
63 cookies_allowed: NO
64 setting:
65 "This request cannot be stopped in settings, but will not be sent "
66 "if the user does not use Chrome Remote Desktop."
67 policy_exception_justification:
68 "Not implemented."
69 })");
70
71 constexpr base::TimeDelta kMinimumHeartbeatInterval =
72 base::TimeDelta::FromMinutes(3);
73 constexpr base::TimeDelta kHeartbeatResponseTimeout =
74 base::TimeDelta::FromSeconds(30);
75 constexpr base::TimeDelta kResendDelayOnHostNotFound =
76 base::TimeDelta::FromSeconds(10);
77 constexpr base::TimeDelta kResendDelayOnUnauthenticated =
78 base::TimeDelta::FromSeconds(10);
79
80 constexpr int kMaxResendOnHostNotFoundCount =
81 12; // 2 minutes (12 x 10 seconds).
82 constexpr int kMaxResendOnUnauthenticatedCount =
83 6; // 1 minute (10 x 6 seconds).
84
85 const net::BackoffEntry::Policy kBackoffPolicy = {
86 // Number of initial errors (in sequence) to ignore before applying
87 // exponential back-off rules.
88 0,
89
90 // Initial delay for exponential back-off in ms. (10s)
91 10000,
92
93 // Factor by which the waiting time will be multiplied.
94 2,
95
96 // Fuzzing percentage. ex: 10% will spread requests randomly
97 // between 90%-100% of the calculated time.
98 0.5,
99
100 // Maximum amount of time we are willing to delay our request in ms. (10m)
101 600000,
102
103 // Time to keep an entry from being discarded even when it
104 // has no significant state, -1 to never discard.
105 -1,
106
107 // Starts with initial delay.
108 false,
109 };
110
GetHostname()111 std::string GetHostname() {
112 #if (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_BSD)
113 return net::GetHostName();
114 #elif defined(OS_WIN)
115 wchar_t buffer[MAX_PATH] = {0};
116 DWORD size = MAX_PATH;
117 if (!::GetComputerNameExW(ComputerNameDnsFullyQualified, buffer, &size)) {
118 PLOG(ERROR) << "GetComputerNameExW failed";
119 return std::string();
120 }
121 std::string hostname;
122 if (!base::UTF16ToUTF8(buffer, size, &hostname)) {
123 LOG(ERROR) << "Failed to convert from UTF16 to UTF8";
124 return std::string();
125 }
126 return hostname;
127 #else
128 return std::string();
129 #endif
130 }
131
132 } // namespace
133
134 class HeartbeatSender::HeartbeatClientImpl final
135 : public HeartbeatSender::HeartbeatClient {
136 public:
137 explicit HeartbeatClientImpl(
138 OAuthTokenGetter* oauth_token_getter,
139 scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
140 ~HeartbeatClientImpl() override;
141
142 void Heartbeat(std::unique_ptr<apis::v1::HeartbeatRequest> request,
143 HeartbeatResponseCallback callback) override;
144 void CancelPendingRequests() override;
145
146 private:
147 ProtobufHttpClient http_client_;
148 DISALLOW_COPY_AND_ASSIGN(HeartbeatClientImpl);
149 };
150
HeartbeatClientImpl(OAuthTokenGetter * oauth_token_getter,scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)151 HeartbeatSender::HeartbeatClientImpl::HeartbeatClientImpl(
152 OAuthTokenGetter* oauth_token_getter,
153 scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
154 : http_client_(ServiceUrls::GetInstance()->remoting_server_endpoint(),
155 oauth_token_getter,
156 url_loader_factory) {}
157
158 HeartbeatSender::HeartbeatClientImpl::~HeartbeatClientImpl() = default;
159
Heartbeat(std::unique_ptr<apis::v1::HeartbeatRequest> request,HeartbeatResponseCallback callback)160 void HeartbeatSender::HeartbeatClientImpl::Heartbeat(
161 std::unique_ptr<apis::v1::HeartbeatRequest> request,
162 HeartbeatResponseCallback callback) {
163 std::string host_offline_reason_or_empty_log =
164 request->has_host_offline_reason()
165 ? (", host_offline_reason: " + request->host_offline_reason())
166 : "";
167 HOST_LOG << "Sending outgoing heartbeat. tachyon_id: "
168 << request->tachyon_id() << host_offline_reason_or_empty_log;
169
170 auto request_config =
171 std::make_unique<ProtobufHttpRequestConfig>(kTrafficAnnotation);
172 request_config->path = kHeartbeatPath;
173 request_config->request_message = std::move(request);
174 auto http_request =
175 std::make_unique<ProtobufHttpRequest>(std::move(request_config));
176 http_request->SetTimeoutDuration(kHeartbeatResponseTimeout);
177 http_request->SetResponseCallback(std::move(callback));
178 http_client_.ExecuteRequest(std::move(http_request));
179 }
180
CancelPendingRequests()181 void HeartbeatSender::HeartbeatClientImpl::CancelPendingRequests() {
182 http_client_.CancelPendingRequests();
183 }
184
185 // end of HeartbeatSender::HeartbeatClientImpl
186
HeartbeatSender(Delegate * delegate,const std::string & host_id,SignalStrategy * signal_strategy,OAuthTokenGetter * oauth_token_getter,Observer * observer,scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,bool is_googler)187 HeartbeatSender::HeartbeatSender(
188 Delegate* delegate,
189 const std::string& host_id,
190 SignalStrategy* signal_strategy,
191 OAuthTokenGetter* oauth_token_getter,
192 Observer* observer,
193 scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
194 bool is_googler)
195 : delegate_(delegate),
196 host_id_(host_id),
197 signal_strategy_(signal_strategy),
198 client_(std::make_unique<HeartbeatClientImpl>(oauth_token_getter,
199 url_loader_factory)),
200 oauth_token_getter_(oauth_token_getter),
201 observer_(observer),
202 backoff_(&kBackoffPolicy) {
203 DCHECK(delegate_);
204 DCHECK(signal_strategy_);
205 DCHECK(observer_);
206
207 signal_strategy_->AddListener(this);
208 OnSignalStrategyStateChange(signal_strategy_->GetState());
209 is_googler_ = is_googler;
210 }
211
~HeartbeatSender()212 HeartbeatSender::~HeartbeatSender() {
213 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
214 signal_strategy_->RemoveListener(this);
215 }
216
SetHostOfflineReason(const std::string & host_offline_reason,const base::TimeDelta & timeout,base::OnceCallback<void (bool success)> ack_callback)217 void HeartbeatSender::SetHostOfflineReason(
218 const std::string& host_offline_reason,
219 const base::TimeDelta& timeout,
220 base::OnceCallback<void(bool success)> ack_callback) {
221 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
222 DCHECK(!host_offline_reason_ack_callback_);
223
224 host_offline_reason_ = host_offline_reason;
225 host_offline_reason_ack_callback_ = std::move(ack_callback);
226 host_offline_reason_timeout_timer_.Start(
227 FROM_HERE, timeout, this, &HeartbeatSender::OnHostOfflineReasonTimeout);
228 if (signal_strategy_->GetState() == SignalStrategy::State::CONNECTED) {
229 SendHeartbeat();
230 }
231 }
232
OnSignalStrategyStateChange(SignalStrategy::State state)233 void HeartbeatSender::OnSignalStrategyStateChange(SignalStrategy::State state) {
234 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
235 switch (state) {
236 case SignalStrategy::State::CONNECTED:
237 SendHeartbeat();
238 break;
239 case SignalStrategy::State::DISCONNECTED:
240 client_->CancelPendingRequests();
241 heartbeat_timer_.AbandonAndStop();
242 break;
243 default:
244 // Do nothing
245 break;
246 }
247 }
248
OnSignalStrategyIncomingStanza(const jingle_xmpp::XmlElement * stanza)249 bool HeartbeatSender::OnSignalStrategyIncomingStanza(
250 const jingle_xmpp::XmlElement* stanza) {
251 return false;
252 }
253
OnHostOfflineReasonTimeout()254 void HeartbeatSender::OnHostOfflineReasonTimeout() {
255 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
256 DCHECK(host_offline_reason_ack_callback_);
257
258 std::move(host_offline_reason_ack_callback_).Run(false);
259 }
260
OnHostOfflineReasonAck()261 void HeartbeatSender::OnHostOfflineReasonAck() {
262 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
263 if (!host_offline_reason_ack_callback_) {
264 DCHECK(!host_offline_reason_timeout_timer_.IsRunning());
265 return;
266 }
267
268 DCHECK(host_offline_reason_timeout_timer_.IsRunning());
269 host_offline_reason_timeout_timer_.AbandonAndStop();
270
271 std::move(host_offline_reason_ack_callback_).Run(true);
272 }
273
SendHeartbeat()274 void HeartbeatSender::SendHeartbeat() {
275 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
276
277 if (signal_strategy_->GetState() != SignalStrategy::State::CONNECTED) {
278 LOG(WARNING) << "Not sending heartbeat because the signal strategy is not "
279 "connected.";
280 return;
281 }
282
283 VLOG(1) << "About to send heartbeat.";
284
285 // Drop previous heartbeat and timer so that it doesn't interfere with the
286 // current one.
287 client_->CancelPendingRequests();
288 heartbeat_timer_.AbandonAndStop();
289
290 client_->Heartbeat(
291 CreateHeartbeatRequest(),
292 base::BindOnce(&HeartbeatSender::OnResponse, base::Unretained(this)));
293 observer_->OnHeartbeatSent();
294 }
295
OnResponse(const ProtobufHttpStatus & status,std::unique_ptr<apis::v1::HeartbeatResponse> response)296 void HeartbeatSender::OnResponse(
297 const ProtobufHttpStatus& status,
298 std::unique_ptr<apis::v1::HeartbeatResponse> response) {
299 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
300
301 if (status.ok()) {
302 backoff_.Reset();
303
304 // Notify listener of the first successful heartbeat.
305 if (!initial_heartbeat_sent_) {
306 delegate_->OnFirstHeartbeatSuccessful();
307 initial_heartbeat_sent_ = true;
308 }
309
310 // Notify caller of SetHostOfflineReason that we got an ack and don't
311 // schedule another heartbeat.
312 if (!host_offline_reason_.empty()) {
313 OnHostOfflineReasonAck();
314 return;
315 }
316
317 if (response->has_remote_command()) {
318 OnRemoteCommand(response->remote_command());
319 }
320 } else {
321 LOG(ERROR) << "Heartbeat failed. Error code: "
322 << static_cast<int>(status.error_code()) << ", "
323 << status.error_message();
324 backoff_.InformOfRequest(false);
325 }
326
327 if (status.error_code() == ProtobufHttpStatus::Code::DEADLINE_EXCEEDED) {
328 LOG(ERROR) << "Heartbeat timed out.";
329 }
330
331 // If the host was registered immediately before it sends a heartbeat,
332 // then server-side latency may prevent the server recognizing the
333 // host ID in the heartbeat. So even if all of the first few heartbeats
334 // get a "host ID not found" error, that's not a good enough reason to
335 // exit.
336 if (status.error_code() == ProtobufHttpStatus::Code::NOT_FOUND &&
337 (initial_heartbeat_sent_ ||
338 (backoff_.failure_count() > kMaxResendOnHostNotFoundCount))) {
339 delegate_->OnHostNotFound();
340 return;
341 }
342
343 if (status.error_code() == ProtobufHttpStatus::Code::UNAUTHENTICATED) {
344 oauth_token_getter_->InvalidateCache();
345 if (backoff_.failure_count() > kMaxResendOnUnauthenticatedCount) {
346 delegate_->OnAuthFailed();
347 return;
348 }
349 }
350
351 // Calculate delay before sending the next message.
352 base::TimeDelta delay;
353 // See CoreErrorDomainTranslator.java for the mapping
354 switch (status.error_code()) {
355 case ProtobufHttpStatus::Code::OK:
356 delay = base::TimeDelta::FromSeconds(response->set_interval_seconds());
357 if (delay < kMinimumHeartbeatInterval) {
358 LOG(WARNING) << "Received suspicious set_interval_seconds: " << delay
359 << ". Using minimum interval: "
360 << kMinimumHeartbeatInterval;
361 delay = kMinimumHeartbeatInterval;
362 }
363 break;
364 case ProtobufHttpStatus::Code::NOT_FOUND:
365 delay = kResendDelayOnHostNotFound;
366 break;
367 case ProtobufHttpStatus::Code::UNAUTHENTICATED:
368 delay = kResendDelayOnUnauthenticated;
369 break;
370 default:
371 delay = backoff_.GetTimeUntilRelease();
372 LOG(ERROR) << "Heartbeat failed due to unexpected error. Will retry in "
373 << delay;
374 break;
375 }
376
377 heartbeat_timer_.Start(FROM_HERE, delay, this,
378 &HeartbeatSender::SendHeartbeat);
379 }
380
OnRemoteCommand(apis::v1::HeartbeatResponse::RemoteCommand remote_command)381 void HeartbeatSender::OnRemoteCommand(
382 apis::v1::HeartbeatResponse::RemoteCommand remote_command) {
383 HOST_LOG << "Received remote command: "
384 << apis::v1::HeartbeatResponse::RemoteCommand_Name(remote_command)
385 << "(" << remote_command << ")";
386 switch (remote_command) {
387 case apis::v1::HeartbeatResponse::RESTART_HOST:
388 delegate_->OnRemoteRestartHost();
389 break;
390 default:
391 LOG(WARNING) << "Unknown remote command: " << remote_command;
392 break;
393 }
394 }
395
396 std::unique_ptr<apis::v1::HeartbeatRequest>
CreateHeartbeatRequest()397 HeartbeatSender::CreateHeartbeatRequest() {
398 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
399
400 auto heartbeat = std::make_unique<apis::v1::HeartbeatRequest>();
401 heartbeat->set_tachyon_id(signal_strategy_->GetLocalAddress().id());
402 heartbeat->set_host_id(host_id_);
403 if (!host_offline_reason_.empty()) {
404 heartbeat->set_host_offline_reason(host_offline_reason_);
405 }
406 heartbeat->set_host_version(STRINGIZE(VERSION));
407 heartbeat->set_host_os_name(GetHostOperatingSystemName());
408 heartbeat->set_host_os_version(GetHostOperatingSystemVersion());
409 heartbeat->set_host_cpu_type(base::SysInfo::OperatingSystemArchitecture());
410 heartbeat->set_is_initial_heartbeat(!initial_heartbeat_sent_);
411
412 if (is_googler_) {
413 std::string hostname = GetHostname();
414 if (!hostname.empty()) {
415 heartbeat->set_hostname(hostname);
416 }
417 }
418
419 return heartbeat;
420 }
421
422 } // namespace remoting
423