blob: f509fe3c01aee0d44747d120eb74d5ee9c22ab64 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/host/daemon_process.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/task/single_thread_task_runner.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "remoting/base/auto_thread_task_runner.h"
#include "remoting/base/constants.h"
#include "remoting/host/base/host_exit_codes.h"
#include "remoting/host/base/screen_resolution.h"
#include "remoting/host/branding.h"
#include "remoting/host/chromoting_messages.h"
#include "remoting/host/config_file_watcher.h"
#include "remoting/host/desktop_session.h"
#include "remoting/host/host_event_logger.h"
#include "remoting/host/host_status_observer.h"
#include "remoting/protocol/transport.h"
namespace remoting {
namespace {
// This is used for tagging system event logs.
const char kApplicationName[] = "chromoting";
std::ostream& operator<<(std::ostream& os, const ScreenResolution& resolution) {
return os << resolution.dimensions().width() << "x"
<< resolution.dimensions().height() << " at "
<< resolution.dpi().x() << "x" << resolution.dpi().y() << " DPI";
}
} // namespace
DaemonProcess::~DaemonProcess() {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
host_event_logger_ = nullptr;
config_watcher_ = nullptr;
DeleteAllDesktopSessions();
}
void DaemonProcess::OnConfigUpdated(const std::string& serialized_config) {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
if (serialized_config_ != serialized_config) {
serialized_config_ = serialized_config;
SendHostConfigToNetworkProcess(serialized_config_);
}
}
void DaemonProcess::OnConfigWatcherError() {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
Stop();
}
void DaemonProcess::OnChannelConnected(int32_t peer_pid) {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
VLOG(1) << "IPC: daemon <- network (" << peer_pid << ")";
DeleteAllDesktopSessions();
// Reset the last known terminal ID because no IDs have been allocated
// by the the newly started process yet.
next_terminal_id_ = 0;
SendHostConfigToNetworkProcess(serialized_config_);
}
bool DaemonProcess::OnMessageReceived(const IPC::Message& message) {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
LOG(ERROR) << "Received unexpected IPC type: " << message.type();
CrashNetworkProcess(FROM_HERE);
return false;
}
void DaemonProcess::OnPermanentError(int exit_code) {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
DCHECK(kMinPermanentErrorExitCode <= exit_code &&
exit_code <= kMaxPermanentErrorExitCode);
Stop();
}
void DaemonProcess::OnWorkerProcessStopped() {
desktop_session_manager_.reset();
host_status_observer_.reset();
}
void DaemonProcess::OnAssociatedInterfaceRequest(
const std::string& interface_name,
mojo::ScopedInterfaceEndpointHandle handle) {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
// Typically we'd want to ensure that an associated receiver was not requested
// multiple times as that would indicate a logic error (or that the calling
// process had possibly been compromised). In the case of the network process,
// which handles network traffic and encoding, it's possible that there is a
// protocol error or OS driver fault which cases the process to crash. When
// that occurs, the daemon process will launch a new instance of the network
// process (which is handled outside of this class) and that new instance will
// attempt to retrieve the set of associated interfaces it needs to do its
// work. If that occurs, we log a warning and allow the new process to set up
// its associated remotes. In other areas of the code we might crash the
// requesting (or current) process but that could lead to a crash loop here.
if (interface_name == mojom::DesktopSessionManager::Name_) {
LOG_IF(WARNING, desktop_session_manager_)
<< "Associated interface requested "
<< "while |desktop_session_manager_| was still bound.";
desktop_session_manager_.reset();
mojo::PendingAssociatedReceiver<mojom::DesktopSessionManager>
pending_receiver(std::move(handle));
desktop_session_manager_.Bind(std::move(pending_receiver));
} else if (interface_name == mojom::HostStatusObserver::Name_) {
LOG_IF(WARNING, host_status_observer_)
<< "Associated interface requested "
<< "while |host_status_observer_| was still bound.";
host_status_observer_.reset();
mojo::PendingAssociatedReceiver<mojom::HostStatusObserver> pending_receiver(
std::move(handle));
host_status_observer_.Bind(std::move(pending_receiver));
} else {
LOG(ERROR) << "Received unexpected associated interface request: "
<< interface_name;
}
}
void DaemonProcess::CloseDesktopSession(int terminal_id) {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
// Validate the supplied terminal ID. An attempt to use a desktop session ID
// that couldn't possibly have been allocated is considered a protocol error
// and the network process will be restarted.
if (!WasTerminalIdAllocated(terminal_id)) {
LOG(ERROR) << "Invalid terminal ID: " << terminal_id;
CrashNetworkProcess(FROM_HERE);
return;
}
DesktopSessionList::iterator i;
for (i = desktop_sessions_.begin(); i != desktop_sessions_.end(); ++i) {
if ((*i)->id() == terminal_id) {
break;
}
}
// It is OK if the terminal ID wasn't found. There is a race between
// the network and daemon processes. Each frees its own recources first and
// notifies the other party if there was something to clean up.
if (i == desktop_sessions_.end())
return;
delete *i;
desktop_sessions_.erase(i);
VLOG(1) << "Daemon: closed desktop session " << terminal_id;
SendTerminalDisconnected(terminal_id);
}
DaemonProcess::DaemonProcess(
scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
scoped_refptr<AutoThreadTaskRunner> io_task_runner,
base::OnceClosure stopped_callback)
: caller_task_runner_(caller_task_runner),
io_task_runner_(io_task_runner),
next_terminal_id_(0),
stopped_callback_(std::move(stopped_callback)),
status_monitor_(new HostStatusMonitor()) {
DCHECK(caller_task_runner->BelongsToCurrentThread());
// TODO(sammc): On OSX, mojo::core::SetMachPortProvider() should be called
// with a base::PortProvider implementation. Add it here when this code is
// used on OSX.
}
void DaemonProcess::CreateDesktopSession(int terminal_id,
const ScreenResolution& resolution,
bool virtual_terminal) {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
// Validate the supplied terminal ID. An attempt to create a desktop session
// with an ID that could possibly have been allocated already is considered
// a protocol error and the network process will be restarted.
if (WasTerminalIdAllocated(terminal_id)) {
LOG(ERROR) << "Invalid terminal ID: " << terminal_id;
CrashNetworkProcess(FROM_HERE);
return;
}
// Terminal IDs cannot be reused. Update the expected next terminal ID.
next_terminal_id_ = std::max(next_terminal_id_, terminal_id + 1);
// Create the desktop session.
std::unique_ptr<DesktopSession> session =
DoCreateDesktopSession(terminal_id, resolution, virtual_terminal);
if (!session) {
LOG(ERROR) << "Failed to create a desktop session.";
SendTerminalDisconnected(terminal_id);
return;
}
VLOG(1) << "Daemon: opened desktop session " << terminal_id;
desktop_sessions_.push_back(session.release());
}
void DaemonProcess::SetScreenResolution(int terminal_id,
const ScreenResolution& resolution) {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
// Validate the supplied terminal ID. An attempt to use a desktop session ID
// that couldn't possibly have been allocated is considered a protocol error
// and the network process will be restarted.
if (!WasTerminalIdAllocated(terminal_id)) {
LOG(ERROR) << "Invalid terminal ID: " << terminal_id;
CrashNetworkProcess(FROM_HERE);
return;
}
// Validate |resolution| and restart the sender if it is not valid.
if (resolution.IsEmpty()) {
LOG(ERROR) << "Invalid resolution specified: " << resolution;
CrashNetworkProcess(FROM_HERE);
return;
}
DesktopSessionList::iterator i;
for (i = desktop_sessions_.begin(); i != desktop_sessions_.end(); ++i) {
if ((*i)->id() == terminal_id) {
break;
}
}
// It is OK if the terminal ID wasn't found. There is a race between
// the network and daemon processes. Each frees its own resources first and
// notifies the other party if there was something to clean up.
if (i == desktop_sessions_.end())
return;
(*i)->SetScreenResolution(resolution);
}
void DaemonProcess::CrashNetworkProcess(const base::Location& location) {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
DoCrashNetworkProcess(location);
DeleteAllDesktopSessions();
}
void DaemonProcess::Initialize() {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
config_watcher_ = std::make_unique<ConfigFileWatcher>(
caller_task_runner(), io_task_runner(), GetConfigPath());
config_watcher_->Watch(this);
host_event_logger_ =
HostEventLogger::Create(status_monitor_, kApplicationName);
// Launch the process.
LaunchNetworkProcess();
}
void DaemonProcess::Stop() {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
OnWorkerProcessStopped();
if (stopped_callback_) {
std::move(stopped_callback_).Run();
}
}
bool DaemonProcess::WasTerminalIdAllocated(int terminal_id) {
return terminal_id < next_terminal_id_;
}
void DaemonProcess::OnClientAccessDenied(const std::string& signaling_id) {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
for (auto& observer : status_monitor_->observers())
observer.OnClientAccessDenied(signaling_id);
}
void DaemonProcess::OnClientAuthenticated(const std::string& signaling_id) {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
for (auto& observer : status_monitor_->observers())
observer.OnClientAuthenticated(signaling_id);
}
void DaemonProcess::OnClientConnected(const std::string& signaling_id) {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
for (auto& observer : status_monitor_->observers())
observer.OnClientConnected(signaling_id);
}
void DaemonProcess::OnClientDisconnected(const std::string& signaling_id) {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
for (auto& observer : status_monitor_->observers())
observer.OnClientDisconnected(signaling_id);
}
void DaemonProcess::OnClientRouteChange(const std::string& signaling_id,
const std::string& channel_name,
const protocol::TransportRoute& route) {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
for (auto& observer : status_monitor_->observers())
observer.OnClientRouteChange(signaling_id, channel_name, route);
}
void DaemonProcess::OnHostStarted(const std::string& owner_email) {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
for (auto& observer : status_monitor_->observers())
observer.OnHostStarted(owner_email);
}
void DaemonProcess::OnHostShutdown() {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
for (auto& observer : status_monitor_->observers())
observer.OnHostShutdown();
}
void DaemonProcess::DeleteAllDesktopSessions() {
while (!desktop_sessions_.empty()) {
delete desktop_sessions_.front();
desktop_sessions_.pop_front();
}
}
base::FilePath DaemonProcess::GetConfigPath() {
base::FilePath config_path;
const base::CommandLine* command_line =
base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(kHostConfigSwitchName)) {
config_path = command_line->GetSwitchValuePath(kHostConfigSwitchName);
} else {
base::FilePath default_config_dir = remoting::GetConfigDir();
config_path = default_config_dir.Append(kDefaultHostConfigFile);
}
return config_path;
}
} // namespace remoting