| // 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 <string> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.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/single_thread_task_runner.h" |
| #include "remoting/base/auto_thread_task_runner.h" |
| #include "remoting/base/constants.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_exit_codes.h" |
| #include "remoting/host/host_status_observer.h" |
| #include "remoting/host/process_stats_sender.h" |
| #include "remoting/host/screen_resolution.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; |
| SendToNetwork( |
| new ChromotingDaemonNetworkMsg_Configuration(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; |
| |
| // Send the configuration to the network process. |
| SendToNetwork( |
| new ChromotingDaemonNetworkMsg_Configuration(serialized_config_)); |
| } |
| |
| bool DaemonProcess::OnMessageReceived(const IPC::Message& message) { |
| DCHECK(caller_task_runner()->BelongsToCurrentThread()); |
| |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP(DaemonProcess, message) |
| IPC_MESSAGE_HANDLER(ChromotingNetworkHostMsg_ConnectTerminal, |
| CreateDesktopSession) |
| IPC_MESSAGE_HANDLER(ChromotingNetworkHostMsg_DisconnectTerminal, |
| CloseDesktopSession) |
| IPC_MESSAGE_HANDLER(ChromotingNetworkDaemonMsg_SetScreenResolution, |
| SetScreenResolution) |
| IPC_MESSAGE_HANDLER(ChromotingNetworkDaemonMsg_AccessDenied, |
| OnAccessDenied) |
| IPC_MESSAGE_HANDLER(ChromotingNetworkDaemonMsg_ClientAuthenticated, |
| OnClientAuthenticated) |
| IPC_MESSAGE_HANDLER(ChromotingNetworkDaemonMsg_ClientConnected, |
| OnClientConnected) |
| IPC_MESSAGE_HANDLER(ChromotingNetworkDaemonMsg_ClientDisconnected, |
| OnClientDisconnected) |
| IPC_MESSAGE_HANDLER(ChromotingNetworkDaemonMsg_ClientRouteChange, |
| OnClientRouteChange) |
| IPC_MESSAGE_HANDLER(ChromotingNetworkDaemonMsg_HostStarted, |
| OnHostStarted) |
| IPC_MESSAGE_HANDLER(ChromotingNetworkDaemonMsg_HostShutdown, |
| OnHostShutdown) |
| IPC_MESSAGE_HANDLER(ChromotingNetworkToAnyMsg_StartProcessStatsReport, |
| StartProcessStatsReport) |
| IPC_MESSAGE_HANDLER(ChromotingNetworkToAnyMsg_StopProcessStatsReport, |
| StopProcessStatsReport) |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| |
| if (!handled) { |
| LOG(ERROR) << "Received unexpected IPC type: " << message.type(); |
| CrashNetworkProcess(FROM_HERE); |
| } |
| |
| return handled; |
| } |
| |
| void DaemonProcess::OnPermanentError(int exit_code) { |
| DCHECK(caller_task_runner()->BelongsToCurrentThread()); |
| DCHECK(kMinPermanentErrorExitCode <= exit_code && |
| exit_code <= kMaxPermanentErrorExitCode); |
| |
| Stop(); |
| } |
| |
| void DaemonProcess::OnWorkerProcessStopped() { |
| process_stats_request_count_ = 0; |
| stats_sender_.reset(); |
| } |
| |
| 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; |
| SendToNetwork( |
| new ChromotingDaemonNetworkMsg_TerminalDisconnected(terminal_id)); |
| } |
| |
| DaemonProcess::DaemonProcess( |
| scoped_refptr<AutoThreadTaskRunner> caller_task_runner, |
| scoped_refptr<AutoThreadTaskRunner> io_task_runner, |
| const base::Closure& stopped_callback) |
| : caller_task_runner_(caller_task_runner), |
| io_task_runner_(io_task_runner), |
| next_terminal_id_(0), |
| stopped_callback_(stopped_callback), |
| status_monitor_(new HostStatusMonitor()), |
| current_process_stats_("DaemonProcess") { |
| DCHECK(caller_task_runner->BelongsToCurrentThread()); |
| // TODO(sammc): On OSX, mojo::edk::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."; |
| SendToNetwork( |
| new ChromotingDaemonNetworkMsg_TerminalDisconnected(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()); |
| |
| const base::CommandLine* command_line = |
| base::CommandLine::ForCurrentProcess(); |
| // Get the name of the host configuration file. |
| base::FilePath default_config_dir = remoting::GetConfigDir(); |
| base::FilePath config_path = default_config_dir.Append( |
| kDefaultHostConfigFile); |
| if (command_line->HasSwitch(kHostConfigSwitchName)) { |
| config_path = command_line->GetSwitchValuePath(kHostConfigSwitchName); |
| } |
| config_watcher_.reset(new ConfigFileWatcher( |
| caller_task_runner(), io_task_runner(), config_path)); |
| 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_.is_null()) { |
| base::ResetAndReturn(&stopped_callback_).Run(); |
| } |
| } |
| |
| bool DaemonProcess::WasTerminalIdAllocated(int terminal_id) { |
| return terminal_id < next_terminal_id_; |
| } |
| |
| void DaemonProcess::OnAccessDenied(const std::string& jid) { |
| DCHECK(caller_task_runner()->BelongsToCurrentThread()); |
| |
| for (auto& observer : status_observers_) |
| observer.OnAccessDenied(jid); |
| } |
| |
| void DaemonProcess::OnClientAuthenticated(const std::string& jid) { |
| DCHECK(caller_task_runner()->BelongsToCurrentThread()); |
| |
| for (auto& observer : status_observers_) |
| observer.OnClientAuthenticated(jid); |
| } |
| |
| void DaemonProcess::OnClientConnected(const std::string& jid) { |
| DCHECK(caller_task_runner()->BelongsToCurrentThread()); |
| |
| for (auto& observer : status_observers_) |
| observer.OnClientConnected(jid); |
| } |
| |
| void DaemonProcess::OnClientDisconnected(const std::string& jid) { |
| DCHECK(caller_task_runner()->BelongsToCurrentThread()); |
| |
| for (auto& observer : status_observers_) |
| observer.OnClientDisconnected(jid); |
| } |
| |
| void DaemonProcess::OnClientRouteChange(const std::string& jid, |
| const std::string& channel_name, |
| const SerializedTransportRoute& route) { |
| DCHECK(caller_task_runner()->BelongsToCurrentThread()); |
| |
| protocol::TransportRoute parsed_route; |
| parsed_route.type = route.type; |
| |
| net::IPAddress remote_ip(route.remote_ip.data(), route.remote_ip.size()); |
| CHECK(remote_ip.empty() || remote_ip.IsValid()); |
| parsed_route.remote_address = net::IPEndPoint(remote_ip, route.remote_port); |
| |
| net::IPAddress local_ip(route.local_ip.data(), route.local_ip.size()); |
| CHECK(local_ip.empty() || local_ip.IsValid()); |
| parsed_route.local_address = net::IPEndPoint(local_ip, route.local_port); |
| |
| for (auto& observer : status_observers_) |
| observer.OnClientRouteChange(jid, channel_name, parsed_route); |
| } |
| |
| void DaemonProcess::OnHostStarted(const std::string& xmpp_login) { |
| DCHECK(caller_task_runner()->BelongsToCurrentThread()); |
| |
| for (auto& observer : status_observers_) |
| observer.OnStart(xmpp_login); |
| } |
| |
| void DaemonProcess::OnHostShutdown() { |
| DCHECK(caller_task_runner()->BelongsToCurrentThread()); |
| |
| for (auto& observer : status_observers_) |
| observer.OnShutdown(); |
| } |
| |
| void DaemonProcess::DeleteAllDesktopSessions() { |
| while (!desktop_sessions_.empty()) { |
| delete desktop_sessions_.front(); |
| desktop_sessions_.pop_front(); |
| } |
| } |
| |
| void DaemonProcess::StartProcessStatsReport(base::TimeDelta interval) { |
| DCHECK(caller_task_runner()->BelongsToCurrentThread()); |
| if (interval <= base::TimeDelta::FromSeconds(0)) { |
| interval = kDefaultProcessStatsInterval; |
| } |
| |
| process_stats_request_count_++; |
| DCHECK_GT(process_stats_request_count_, 0); |
| if (process_stats_request_count_ == 1 || |
| stats_sender_->interval() > interval) { |
| DCHECK_EQ(process_stats_request_count_ == 1, !stats_sender_); |
| stats_sender_.reset(new ProcessStatsSender( |
| this, |
| interval, |
| { ¤t_process_stats_ })); |
| } |
| } |
| |
| void DaemonProcess::StopProcessStatsReport() { |
| DCHECK(caller_task_runner()->BelongsToCurrentThread()); |
| process_stats_request_count_--; |
| DCHECK_GE(process_stats_request_count_, 0); |
| if (process_stats_request_count_ == 0) { |
| DCHECK(stats_sender_); |
| stats_sender_.reset(); |
| } |
| } |
| |
| void DaemonProcess::OnProcessStats( |
| const protocol::AggregatedProcessResourceUsage& usage) { |
| SendToNetwork(new ChromotingAnyToNetworkMsg_ReportProcessStats(usage)); |
| } |
| |
| } // namespace remoting |