diff options
author | alexeypa@chromium.org <alexeypa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-17 21:06:24 +0000 |
---|---|---|
committer | alexeypa@chromium.org <alexeypa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-17 21:06:24 +0000 |
commit | 60ccc243dfbfc86001cf1c0a4b44131a39d7d28d (patch) | |
tree | 8834337b7ad8eec985fd783d382734c5d9388eed /remoting | |
parent | f05d582b7e213b15138697c01abf48c652ff271a (diff) | |
download | chromium_src-60ccc243dfbfc86001cf1c0a4b44131a39d7d28d.zip chromium_src-60ccc243dfbfc86001cf1c0a4b44131a39d7d28d.tar.gz chromium_src-60ccc243dfbfc86001cf1c0a4b44131a39d7d28d.tar.bz2 |
[Chromoting] Added support of a list of desktop sessions on the daemon side.
DaemonProcess object keeps a list of active desktop sessions, each representing the screen, input devices, etc to be remoted to another machine. The network process is expected to request a desktop session to be opened once a connection has been accepted and closed when the client session is destroyed. This CL includes changes on the daemon side only.
BUG=134694
Review URL: https://chromiumcodereview.appspot.com/11017065
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@162520 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
-rw-r--r-- | remoting/host/chromoting_messages.h | 20 | ||||
-rw-r--r-- | remoting/host/daemon_process.cc | 125 | ||||
-rw-r--r-- | remoting/host/daemon_process.h | 67 | ||||
-rw-r--r-- | remoting/host/daemon_process_unittest.cc | 312 | ||||
-rw-r--r-- | remoting/host/daemon_process_win.cc | 81 | ||||
-rw-r--r-- | remoting/host/desktop_session.cc | 20 | ||||
-rw-r--r-- | remoting/host/desktop_session.h | 42 | ||||
-rw-r--r-- | remoting/host/desktop_session_win.cc | 103 | ||||
-rw-r--r-- | remoting/host/desktop_session_win.h | 77 | ||||
-rw-r--r-- | remoting/host/remoting_me2me_host.cc | 6 | ||||
-rw-r--r-- | remoting/host/win/host_service.cc | 4 | ||||
-rw-r--r-- | remoting/host/win/worker_process_launcher.cc | 6 | ||||
-rw-r--r-- | remoting/remoting.gyp | 26 |
13 files changed, 815 insertions, 74 deletions
diff --git a/remoting/host/chromoting_messages.h b/remoting/host/chromoting_messages.h index f997f47..ad85120 100644 --- a/remoting/host/chromoting_messages.h +++ b/remoting/host/chromoting_messages.h @@ -10,7 +10,25 @@ #define IPC_MESSAGE_START ChromotingMsgStart //----------------------------------------------------------------------------- -// Daemon to Network messages +// Chromoting messages sent from the daemon to the network process. // Delivers the host configuration (and updates) to the network process. IPC_MESSAGE_CONTROL1(ChromotingDaemonNetworkMsg_Configuration, std::string) + +// Notifies the network process that the terminal |terminal_id| has been +// disconnected from the desktop session. +IPC_MESSAGE_CONTROL1(ChromotingDaemonNetworkMsg_TerminalDisconnected, + int /* terminal_id */) + +//----------------------------------------------------------------------------- +// Chromoting messages sent from the network to the daemon process. + +// Connects the terminal |terminal_id| (i.e. the remote client) to a desktop +// session. +IPC_MESSAGE_CONTROL1(ChromotingNetworkHostMsg_ConnectTerminal, + int /* terminal_id */) + +// Disconnects the terminal |terminal_id| from the desktop session it was +// connected to. +IPC_MESSAGE_CONTROL1(ChromotingNetworkHostMsg_DisconnectTerminal, + int /* terminal_id */) diff --git a/remoting/host/daemon_process.cc b/remoting/host/daemon_process.cc index 720ed4b..e83db6c 100644 --- a/remoting/host/daemon_process.cc +++ b/remoting/host/daemon_process.cc @@ -12,58 +12,111 @@ #include "base/single_thread_task_runner.h" #include "remoting/host/branding.h" #include "remoting/host/chromoting_messages.h" +#include "remoting/host/desktop_session.h" namespace remoting { DaemonProcess::~DaemonProcess() { - CHECK(!config_watcher_.get()); + DCHECK(!config_watcher_.get()); + DCHECK(desktop_sessions_.empty()); } void DaemonProcess::OnConfigUpdated(const std::string& serialized_config) { + DCHECK(caller_task_runner()->BelongsToCurrentThread()); + if (serialized_config_ != serialized_config) { serialized_config_ = serialized_config; - Send(new ChromotingDaemonNetworkMsg_Configuration(serialized_config_)); + SendToNetwork( + new ChromotingDaemonNetworkMsg_Configuration(serialized_config_)); } } void DaemonProcess::OnConfigWatcherError() { + DCHECK(caller_task_runner()->BelongsToCurrentThread()); + Stop(); } void DaemonProcess::OnChannelConnected() { - DCHECK(main_task_runner()->BelongsToCurrentThread()); + DCHECK(caller_task_runner()->BelongsToCurrentThread()); + + DeleteAllDesktopSessions(); + + // Reset the last known desktop session 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. - Send(new ChromotingDaemonNetworkMsg_Configuration(serialized_config_)); + SendToNetwork( + new ChromotingDaemonNetworkMsg_Configuration(serialized_config_)); } bool DaemonProcess::OnMessageReceived(const IPC::Message& message) { - DCHECK(main_task_runner()->BelongsToCurrentThread()); - - return false; + 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_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; } void DaemonProcess::OnPermanentError() { - DCHECK(main_task_runner()->BelongsToCurrentThread()); - + DCHECK(caller_task_runner()->BelongsToCurrentThread()); Stop(); } +void DaemonProcess::CloseDesktopSession(int terminal_id) { + DCHECK(caller_task_runner()->BelongsToCurrentThread()); + + // Validate the supplied terminal ID. An attempt to close a desktop session + // with an ID that couldn't possibly have been allocated is considered + // a protocol error and the network process will be restarted. + if (!IsTerminalIdKnown(terminal_id)) { + LOG(ERROR) << "An invalid terminal ID. terminal_id=" << terminal_id; + RestartNetworkProcess(); + DeleteAllDesktopSessions(); + 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 desktop session 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<base::SingleThreadTaskRunner> main_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, const base::Closure& stopped_callback) - : Stoppable(main_task_runner, stopped_callback), - main_task_runner_(main_task_runner), - io_task_runner_(io_task_runner) { - // Initialize on the same thread that will be used for shutting down. - main_task_runner_->PostTask( - FROM_HERE, - base::Bind(&DaemonProcess::Initialize, base::Unretained(this))); + : Stoppable(caller_task_runner, stopped_callback), + caller_task_runner_(caller_task_runner), + io_task_runner_(io_task_runner), + next_terminal_id_(0) { + DCHECK(caller_task_runner->BelongsToCurrentThread()); } void DaemonProcess::Initialize() { - DCHECK(main_task_runner()->BelongsToCurrentThread()); + DCHECK(caller_task_runner()->BelongsToCurrentThread()); // Get the name of the host configuration file. FilePath default_config_dir = remoting::GetConfigDir(); @@ -74,7 +127,7 @@ void DaemonProcess::Initialize() { } // Start watching the host configuration file. - config_watcher_.reset(new ConfigFileWatcher(main_task_runner(), + config_watcher_.reset(new ConfigFileWatcher(caller_task_runner(), io_task_runner(), this)); config_watcher_->Watch(config_path); @@ -83,11 +136,43 @@ void DaemonProcess::Initialize() { LaunchNetworkProcess(); } +bool DaemonProcess::IsTerminalIdKnown(int terminal_id) { + return terminal_id < next_terminal_id_; +} + +void DaemonProcess::CreateDesktopSession(int terminal_id) { + 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 (IsTerminalIdKnown(terminal_id)) { + LOG(ERROR) << "An invalid terminal ID. terminal_id=" << terminal_id; + RestartNetworkProcess(); + DeleteAllDesktopSessions(); + return; + } + + VLOG(1) << "Daemon: opened desktop session " << terminal_id; + desktop_sessions_.push_back( + DoCreateDesktopSession(terminal_id).release()); + next_terminal_id_ = std::max(next_terminal_id_, terminal_id + 1); +} + void DaemonProcess::DoStop() { - DCHECK(main_task_runner()->BelongsToCurrentThread()); + DCHECK(caller_task_runner()->BelongsToCurrentThread()); config_watcher_.reset(); + DeleteAllDesktopSessions(); + CompleteStopping(); } +void DaemonProcess::DeleteAllDesktopSessions() { + while (!desktop_sessions_.empty()) { + delete desktop_sessions_.front(); + desktop_sessions_.pop_front(); + } +} + } // namespace remoting diff --git a/remoting/host/daemon_process.h b/remoting/host/daemon_process.h index b7bcc55..c2ba4e2 100644 --- a/remoting/host/daemon_process.h +++ b/remoting/host/daemon_process.h @@ -5,6 +5,8 @@ #ifndef REMOTING_HOST_DAEMON_PROCESS_H_ #define REMOTING_HOST_DAEMON_PROCESS_H_ +#include <list> + #include "base/basictypes.h" #include "base/compiler_specific.h" #include "base/memory/ref_counted.h" @@ -23,19 +25,26 @@ class SingleThreadTaskRunner; namespace remoting { +class DesktopSession; + // This class implements core of the daemon process. It manages the networking -// process running at lower privileges and maintains the list of virtual -// terminals. +// process running at lower privileges and maintains the list of desktop +// sessions. class DaemonProcess : public Stoppable, public ConfigFileWatcher::Delegate, public WorkerProcessIpcDelegate { public: + typedef std::list<DesktopSession*> DesktopSessionList; + virtual ~DaemonProcess(); - // Creates a platform-specific implementation of the daemon process object. + // Creates a platform-specific implementation of the daemon process object + // passing relevant task runners. Public methods of this class must be called + // on the |caller_task_runner| thread. |io_task_runner| is used to handle IPC + // and background I/O tasks. static scoped_ptr<DaemonProcess> Create( - scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, const base::Closure& stopped_callback); @@ -50,41 +59,73 @@ class DaemonProcess // Sends an IPC message to the network process. The message will be dropped // unless the network process is connected over the IPC channel. - virtual void Send(IPC::Message* message) = 0; + virtual void SendToNetwork(IPC::Message* message) = 0; + + // Closes the desktop session identified by |terminal_id|. + void CloseDesktopSession(int terminal_id); protected: - DaemonProcess(scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, + DaemonProcess(scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, const base::Closure& stopped_callback); - // Reads the host configuration and launches the networking process. + // Reads the host configuration and launches the network process. void Initialize(); + // Creates a desktop session and assigns a unique ID to it. + void CreateDesktopSession(int terminal_id); + + // Returns true if |terminal_id| is considered to be known. I.e. it is + // less or equal to the highest ID we have seen so far. + bool IsTerminalIdKnown(int terminal_id); + // Stoppable implementation. virtual void DoStop() OVERRIDE; + // Creates a platform-specific desktop session and assigns a unique ID to it. + virtual scoped_ptr<DesktopSession> DoCreateDesktopSession( + int terminal_id) = 0; + // Launches the network process and establishes an IPC channel with it. virtual void LaunchNetworkProcess() = 0; - scoped_refptr<base::SingleThreadTaskRunner> main_task_runner() { - return main_task_runner_; + // Restart the network process. + virtual void RestartNetworkProcess() = 0; + + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner() { + return caller_task_runner_; } scoped_refptr<base::SingleThreadTaskRunner> io_task_runner() { return io_task_runner_; } + // Let the test code analyze the list of desktop sessions. + friend class DaemonProcessTest; + const DesktopSessionList& desktop_sessions() const { + return desktop_sessions_; + } + private: + // Deletes all desktop sessions. + void DeleteAllDesktopSessions(); + + // Task runner on which public methods of this class must be called. + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_; + + // Handles IPC and background I/O tasks. + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; + scoped_ptr<ConfigFileWatcher> config_watcher_; // The configuration file contents. std::string serialized_config_; - // The main task runner. Typically it is the UI message loop. - scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; + // The list of active desktop sessions. + DesktopSessionList desktop_sessions_; - // Handles IPC and background I/O tasks. - scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; + // The highest desktop session ID that has been seen so far. + int next_terminal_id_; DISALLOW_COPY_AND_ASSIGN(DaemonProcess); }; diff --git a/remoting/host/daemon_process_unittest.cc b/remoting/host/daemon_process_unittest.cc new file mode 100644 index 0000000..29a1dd2 --- /dev/null +++ b/remoting/host/daemon_process_unittest.cc @@ -0,0 +1,312 @@ +// 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 "base/bind.h" +#include "base/bind_helpers.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "ipc/ipc_message.h" +#include "ipc/ipc_message_macros.h" +#include "remoting/base/auto_thread_task_runner.h" +#include "remoting/host/chromoting_messages.h" +#include "remoting/host/daemon_process.h" +#include "remoting/host/desktop_session.h" +#include "testing/gmock_mutant.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using testing::AnyNumber; +using testing::InSequence; + +namespace remoting { + +namespace { + +enum Messages { + kMessageConfiguration = ChromotingDaemonNetworkMsg_Configuration::ID, + kMessageConnectTerminal = ChromotingNetworkHostMsg_ConnectTerminal::ID, + kMessageDisconnectTerminal = ChromotingNetworkHostMsg_DisconnectTerminal::ID, + kMessageTerminalDisconnected = + ChromotingDaemonNetworkMsg_TerminalDisconnected::ID +}; + +// Provides a public constructor allowing the test to create instances of +// DesktopSession directly. +class FakeDesktopSession : public DesktopSession { + public: + FakeDesktopSession(DaemonProcess* daemon_process, int id); + virtual ~FakeDesktopSession(); + + private: + DISALLOW_COPY_AND_ASSIGN(FakeDesktopSession); +}; + +class MockDaemonProcess : public DaemonProcess { + public: + MockDaemonProcess( + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, + const base::Closure& stopped_callback); + virtual ~MockDaemonProcess(); + + virtual scoped_ptr<DesktopSession> DoCreateDesktopSession( + int terminal_id) OVERRIDE; + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + virtual void SendToNetwork(IPC::Message* message) OVERRIDE; + + MOCK_METHOD1(Received, void(const IPC::Message&)); + MOCK_METHOD1(Sent, void(const IPC::Message&)); + + MOCK_METHOD1(DoCreateDesktopSessionPtr, DesktopSession*(int)); + MOCK_METHOD0(LaunchNetworkProcess, void()); + MOCK_METHOD0(RestartNetworkProcess, void()); + + private: + DISALLOW_COPY_AND_ASSIGN(MockDaemonProcess); +}; + +FakeDesktopSession::FakeDesktopSession(DaemonProcess* daemon_process, int id) + : DesktopSession(daemon_process, id) { +} + +FakeDesktopSession::~FakeDesktopSession() { +} + +MockDaemonProcess::MockDaemonProcess( + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, + const base::Closure& stopped_callback) + : DaemonProcess(caller_task_runner, io_task_runner, stopped_callback) { +} + +MockDaemonProcess::~MockDaemonProcess() { +} + +scoped_ptr<DesktopSession> MockDaemonProcess::DoCreateDesktopSession( + int terminal_id) { + return scoped_ptr<DesktopSession>(DoCreateDesktopSessionPtr(terminal_id)); +} + +bool MockDaemonProcess::OnMessageReceived(const IPC::Message& message) { + // Notify the mock method. + Received(message); + + // Call the actual handler. + return DaemonProcess::OnMessageReceived(message); +} + +void MockDaemonProcess::SendToNetwork(IPC::Message* message) { + // Notify the mock method. + Sent(*message); +} + +} // namespace + +class DaemonProcessTest : public testing::Test { + public: + DaemonProcessTest(); + virtual ~DaemonProcessTest(); + + virtual void SetUp() OVERRIDE; + virtual void TearDown() OVERRIDE; + + // DaemonProcess mocks + DesktopSession* DoCreateDesktopSession(int terminal_id); + void LaunchNetworkProcess(); + + // Deletes |daemon_process_|. + void DeleteDaemonProcess(); + + // Quits |message_loop_|. + void QuitMessageLoop(); + + void StartDaemonProcess(); + + const DaemonProcess::DesktopSessionList& desktop_sessions() const { + return daemon_process_->desktop_sessions(); + } + + protected: + MessageLoop message_loop_; + + scoped_ptr<MockDaemonProcess> daemon_process_; + int terminal_id_; +}; + + +DaemonProcessTest::DaemonProcessTest() + : message_loop_(MessageLoop::TYPE_IO), + terminal_id_(0) { +} + +DaemonProcessTest::~DaemonProcessTest() { +} + +void DaemonProcessTest::SetUp() { + scoped_refptr<AutoThreadTaskRunner> task_runner = new AutoThreadTaskRunner( + message_loop_.message_loop_proxy(), + base::Bind(&DaemonProcessTest::QuitMessageLoop, + base::Unretained(this))); + daemon_process_.reset( + new MockDaemonProcess(task_runner, task_runner, + base::Bind(&DaemonProcessTest::DeleteDaemonProcess, + base::Unretained(this)))); + + // Set up daemon process mocks. + EXPECT_CALL(*daemon_process_, DoCreateDesktopSessionPtr(_)) + .Times(AnyNumber()) + .WillRepeatedly(Invoke(this, &DaemonProcessTest::DoCreateDesktopSession)); + EXPECT_CALL(*daemon_process_, LaunchNetworkProcess()) + .Times(AnyNumber()) + .WillRepeatedly(Invoke(this, &DaemonProcessTest::LaunchNetworkProcess)); +} + +void DaemonProcessTest::TearDown() { + daemon_process_->Stop(); + message_loop_.Run(); +} + +DesktopSession* DaemonProcessTest::DoCreateDesktopSession(int terminal_id) { + return new FakeDesktopSession(daemon_process_.get(), terminal_id); +} + +void DaemonProcessTest::LaunchNetworkProcess() { + terminal_id_ = 0; + daemon_process_->OnChannelConnected(); +} + +void DaemonProcessTest::DeleteDaemonProcess() { + daemon_process_.reset(); +} + +void DaemonProcessTest::QuitMessageLoop() { + message_loop_.PostTask(FROM_HERE, MessageLoop::QuitClosure()); +} + +void DaemonProcessTest::StartDaemonProcess() { + // DaemonProcess::Initialize() sets up the config watcher that this test does + // not support. Launch the process directly. + daemon_process_->LaunchNetworkProcess(); +} + +MATCHER_P(Message, type, "") { + return arg.type() == static_cast<uint32>(type); +} + +TEST_F(DaemonProcessTest, OpenClose) { + InSequence s; + EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); + EXPECT_CALL(*daemon_process_, Received(Message(kMessageConnectTerminal))); + EXPECT_CALL(*daemon_process_, Received(Message(kMessageDisconnectTerminal))); + EXPECT_CALL(*daemon_process_, Sent(Message(kMessageTerminalDisconnected))); + + StartDaemonProcess(); + + int id = terminal_id_++; + + EXPECT_TRUE(daemon_process_->OnMessageReceived( + ChromotingNetworkHostMsg_ConnectTerminal(id))); + EXPECT_EQ(1u, desktop_sessions().size()); + EXPECT_EQ(id, desktop_sessions().front()->id()); + + EXPECT_TRUE(daemon_process_->OnMessageReceived( + ChromotingNetworkHostMsg_DisconnectTerminal(id))); + EXPECT_TRUE(desktop_sessions().empty()); +} + +TEST_F(DaemonProcessTest, CallCloseDesktopSession) { + InSequence s; + EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); + EXPECT_CALL(*daemon_process_, Received(Message(kMessageConnectTerminal))); + EXPECT_CALL(*daemon_process_, Sent(Message(kMessageTerminalDisconnected))); + + StartDaemonProcess(); + + int id = terminal_id_++; + + EXPECT_TRUE(daemon_process_->OnMessageReceived( + ChromotingNetworkHostMsg_ConnectTerminal(id))); + EXPECT_EQ(1u, desktop_sessions().size()); + EXPECT_EQ(id, desktop_sessions().front()->id()); + + daemon_process_->CloseDesktopSession(id); + EXPECT_TRUE(desktop_sessions().empty()); +} + +// Sends two CloseDesktopSession messages and expects the second one to be +// ignored. +TEST_F(DaemonProcessTest, DoubleDisconnectTerminal) { + InSequence s; + EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); + EXPECT_CALL(*daemon_process_, Received(Message(kMessageConnectTerminal))); + EXPECT_CALL(*daemon_process_, Received(Message(kMessageDisconnectTerminal))); + EXPECT_CALL(*daemon_process_, Sent(Message(kMessageTerminalDisconnected))); + EXPECT_CALL(*daemon_process_, Received(Message(kMessageDisconnectTerminal))); + + StartDaemonProcess(); + + int id = terminal_id_++; + + EXPECT_TRUE(daemon_process_->OnMessageReceived( + ChromotingNetworkHostMsg_ConnectTerminal(id))); + EXPECT_EQ(1u, desktop_sessions().size()); + EXPECT_EQ(id, desktop_sessions().front()->id()); + + EXPECT_TRUE(daemon_process_->OnMessageReceived( + ChromotingNetworkHostMsg_DisconnectTerminal(id))); + EXPECT_TRUE(desktop_sessions().empty()); + + EXPECT_TRUE(daemon_process_->OnMessageReceived( + ChromotingNetworkHostMsg_DisconnectTerminal(id))); + EXPECT_TRUE(desktop_sessions().empty()); +} + +// Tries to close an invalid terminal ID and expects the network process to be +// restarted. +TEST_F(DaemonProcessTest, InvalidDisconnectTerminal) { + InSequence s; + EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); + EXPECT_CALL(*daemon_process_, Received(Message(kMessageDisconnectTerminal))); + EXPECT_CALL(*daemon_process_, RestartNetworkProcess()) + .WillRepeatedly(Invoke(this, &DaemonProcessTest::LaunchNetworkProcess)); + EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); + + StartDaemonProcess(); + + int id = terminal_id_++; + + EXPECT_TRUE(daemon_process_->OnMessageReceived( + ChromotingNetworkHostMsg_DisconnectTerminal(id))); + EXPECT_TRUE(desktop_sessions().empty()); + EXPECT_EQ(0, terminal_id_); +} + +// Tries to open an invalid terminal ID and expects the network process to be +// restarted. +TEST_F(DaemonProcessTest, InvalidConnectTerminal) { + InSequence s; + EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); + EXPECT_CALL(*daemon_process_, Received(Message(kMessageConnectTerminal))); + EXPECT_CALL(*daemon_process_, Received(Message(kMessageConnectTerminal))); + EXPECT_CALL(*daemon_process_, RestartNetworkProcess()) + .WillRepeatedly(Invoke(this, &DaemonProcessTest::LaunchNetworkProcess)); + EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); + + StartDaemonProcess(); + + int id = terminal_id_++; + + EXPECT_TRUE(daemon_process_->OnMessageReceived( + ChromotingNetworkHostMsg_ConnectTerminal(id))); + EXPECT_EQ(1u, desktop_sessions().size()); + EXPECT_EQ(id, desktop_sessions().front()->id()); + + EXPECT_TRUE(daemon_process_->OnMessageReceived( + ChromotingNetworkHostMsg_ConnectTerminal(id))); + EXPECT_TRUE(desktop_sessions().empty()); + EXPECT_EQ(0, terminal_id_); +} + +} // namespace remoting diff --git a/remoting/host/daemon_process_win.cc b/remoting/host/daemon_process_win.cc index 0a9329a..cb81b4a 100644 --- a/remoting/host/daemon_process_win.cc +++ b/remoting/host/daemon_process_win.cc @@ -15,8 +15,13 @@ #include "base/timer.h" #include "base/utf_string_conversions.h" #include "base/win/scoped_handle.h" +#include "ipc/ipc_message.h" +#include "ipc/ipc_message_macros.h" +#include "remoting/host/chromoting_messages.h" +#include "remoting/host/desktop_session_win.h" #include "remoting/host/host_exit_codes.h" #include "remoting/host/ipc_consts.h" +#include "remoting/host/win/host_service.h" #include "remoting/host/win/launch_process_with_token.h" #include "remoting/host/win/unprivileged_process_delegate.h" #include "remoting/host/win/worker_process_launcher.h" @@ -26,35 +31,41 @@ using base::TimeDelta; namespace remoting { +class WtsConsoleMonitor; + class DaemonProcessWin : public DaemonProcess { public: - DaemonProcessWin(scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, - scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, - const base::Closure& stopped_callback); + DaemonProcessWin( + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, + const base::Closure& stopped_callback); virtual ~DaemonProcessWin(); // Sends an IPC message to the worker process. This method can be called only // after successful Start() and until Stop() is called or an error occurred. - virtual void Send(IPC::Message* message) OVERRIDE; + virtual void SendToNetwork(IPC::Message* message) OVERRIDE; protected: // Stoppable implementation. virtual void DoStop() OVERRIDE; // DaemonProcess implementation. + virtual scoped_ptr<DesktopSession> DoCreateDesktopSession( + int terminal_id) OVERRIDE; virtual void LaunchNetworkProcess() OVERRIDE; + virtual void RestartNetworkProcess() OVERRIDE; private: - scoped_ptr<WorkerProcessLauncher> launcher_; + scoped_ptr<WorkerProcessLauncher> network_launcher_; DISALLOW_COPY_AND_ASSIGN(DaemonProcessWin); }; DaemonProcessWin::DaemonProcessWin( - scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, const base::Closure& stopped_callback) - : DaemonProcess(main_task_runner, io_task_runner, stopped_callback) { + : DaemonProcess(caller_task_runner, io_task_runner, stopped_callback) { } DaemonProcessWin::~DaemonProcessWin() { @@ -64,9 +75,33 @@ DaemonProcessWin::~DaemonProcessWin() { CHECK_EQ(stoppable_state(), Stoppable::kStopped); } +void DaemonProcessWin::SendToNetwork(IPC::Message* message) { + if (network_launcher_) { + network_launcher_->Send(message); + } else { + delete message; + } +} + +void DaemonProcessWin::DoStop() { + DCHECK(caller_task_runner()->BelongsToCurrentThread()); + + network_launcher_.reset(); + DaemonProcess::DoStop(); +} + +scoped_ptr<DesktopSession> DaemonProcessWin::DoCreateDesktopSession( + int terminal_id) { + DCHECK(caller_task_runner()->BelongsToCurrentThread()); + + return scoped_ptr<DesktopSession>(new DesktopSessionWin( + caller_task_runner(), io_task_runner(), this, terminal_id, + HostService::GetInstance())); +} + void DaemonProcessWin::LaunchNetworkProcess() { - DCHECK(main_task_runner()->BelongsToCurrentThread()); - DCHECK(launcher_.get() == NULL); + DCHECK(caller_task_runner()->BelongsToCurrentThread()); + DCHECK(!network_launcher_); // Construct the host binary name. FilePath host_binary; @@ -76,33 +111,27 @@ void DaemonProcessWin::LaunchNetworkProcess() { } scoped_ptr<UnprivilegedProcessDelegate> delegate( - new UnprivilegedProcessDelegate(main_task_runner(), io_task_runner(), + new UnprivilegedProcessDelegate(caller_task_runner(), io_task_runner(), host_binary)); - launcher_.reset(new WorkerProcessLauncher( - main_task_runner(), delegate.Pass(), this)); -} - -void DaemonProcessWin::Send(IPC::Message* message) { - if (launcher_.get() != NULL) { - launcher_->Send(message); - } else { - delete message; - } + network_launcher_.reset(new WorkerProcessLauncher( + caller_task_runner(), delegate.Pass(), this)); } -void DaemonProcessWin::DoStop() { - DCHECK(main_task_runner()->BelongsToCurrentThread()); +void DaemonProcessWin::RestartNetworkProcess() { + DCHECK(caller_task_runner()->BelongsToCurrentThread()); - launcher_.reset(); - DaemonProcess::DoStop(); + network_launcher_.reset(); + LaunchNetworkProcess(); } scoped_ptr<DaemonProcess> DaemonProcess::Create( - scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, const base::Closure& stopped_callback) { scoped_ptr<DaemonProcessWin> daemon_process( - new DaemonProcessWin(main_task_runner, io_task_runner, stopped_callback)); + new DaemonProcessWin(caller_task_runner, io_task_runner, + stopped_callback)); + daemon_process->Initialize(); return daemon_process.PassAs<DaemonProcess>(); } diff --git a/remoting/host/desktop_session.cc b/remoting/host/desktop_session.cc new file mode 100644 index 0000000..b8a9a9d --- /dev/null +++ b/remoting/host/desktop_session.cc @@ -0,0 +1,20 @@ +// 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/desktop_session.h" + +#include "base/single_thread_task_runner.h" +#include "remoting/host/daemon_process.h" + +namespace remoting { + +DesktopSession::~DesktopSession() { +} + +DesktopSession::DesktopSession(DaemonProcess* daemon_process, int id) + : daemon_process_(daemon_process), + id_(id) { +} + +} // namespace remoting diff --git a/remoting/host/desktop_session.h b/remoting/host/desktop_session.h new file mode 100644 index 0000000..f59818b --- /dev/null +++ b/remoting/host/desktop_session.h @@ -0,0 +1,42 @@ +// 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. + +#ifndef REMOTING_HOST_DESKTOP_SESSION_H_ +#define REMOTING_HOST_DESKTOP_SESSION_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" + +namespace remoting { + +class DaemonProcess; + +// Represents the desktop session for a connected terminal. Each desktop session +// has a unique identifier used by cross-platform code to refer to it. +class DesktopSession { + public: + virtual ~DesktopSession(); + + int id() const { return id_; } + + protected: + // Creates a terminal and assigns a unique identifier to it. |deamon_process| + // must outlive |this|. + DesktopSession(DaemonProcess* daemon_process, int id); + + DaemonProcess* daemon_process() const { return daemon_process_; } + + private: + // The owner of |this|. + DaemonProcess* const daemon_process_; + + // A unique identifier of the terminal. + const int id_; + + DISALLOW_COPY_AND_ASSIGN(DesktopSession); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_DESKTOP_SESSION_H_ diff --git a/remoting/host/desktop_session_win.cc b/remoting/host/desktop_session_win.cc new file mode 100644 index 0000000..6224c3d --- /dev/null +++ b/remoting/host/desktop_session_win.cc @@ -0,0 +1,103 @@ +// 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/desktop_session_win.h" + +#include "base/path_service.h" +#include "base/single_thread_task_runner.h" +#include "remoting/host/daemon_process.h" +#include "remoting/host/win/worker_process_launcher.h" +#include "remoting/host/win/wts_console_monitor.h" +#include "remoting/host/win/wts_session_process_delegate.h" + +namespace { + +const FilePath::CharType kDesktopBinaryName[] = + FILE_PATH_LITERAL("remoting_desktop.exe"); + +// The security descriptor of the daemon IPC endpoint. It gives full access +// to LocalSystem and denies access by anyone else. +const char kDaemonIpcSecurityDescriptor[] = "O:SYG:SYD:(A;;GA;;;SY)"; + +} // namespace + +namespace remoting { + +DesktopSessionWin::DesktopSessionWin( + scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, + DaemonProcess* daemon_process, + int id, + WtsConsoleMonitor* monitor) + : DesktopSession(daemon_process, id), + main_task_runner_(main_task_runner), + io_task_runner_(io_task_runner), + monitor_(monitor) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + monitor_->AddWtsConsoleObserver(this); +} + +DesktopSessionWin::~DesktopSessionWin() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + launcher_.reset(); + monitor_->RemoveWtsConsoleObserver(this); +} + +void DesktopSessionWin::OnChannelConnected() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); +} + +bool DesktopSessionWin::OnMessageReceived(const IPC::Message& message) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + // TODO(alexeypa): Process ChromotingDesktopHostMsg_Initialized messages here. + // See http://crbug.com/134694. + return false; +} + +void DesktopSessionWin::OnPermanentError() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + launcher_.reset(); + + // This call will delete |this| so it should be at the very end of the method. + daemon_process()->CloseDesktopSession(id()); +} + +void DesktopSessionWin::OnSessionAttached(uint32 session_id) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK(launcher_.get() == NULL); + + // Construct the host binary name. + if (desktop_binary_.empty()) { + FilePath dir_path; + if (!PathService::Get(base::DIR_EXE, &dir_path)) { + LOG(ERROR) << "Failed to get the executable file name."; + OnPermanentError(); + return; + } + desktop_binary_ = dir_path.Append(kDesktopBinaryName); + } + + // Create a delegate to launch a process into the session. + scoped_ptr<WtsSessionProcessDelegate> delegate( + new WtsSessionProcessDelegate(main_task_runner_, io_task_runner_, + desktop_binary_, session_id, + true, kDaemonIpcSecurityDescriptor)); + + // Create a launcher for the desktop process, using the per-session delegate. + launcher_.reset(new WorkerProcessLauncher( + main_task_runner_, delegate.Pass(), this)); +} + +void DesktopSessionWin::OnSessionDetached() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK(launcher_.get() != NULL); + + launcher_.reset(); +} + +} // namespace remoting diff --git a/remoting/host/desktop_session_win.h b/remoting/host/desktop_session_win.h new file mode 100644 index 0000000..f958225 --- /dev/null +++ b/remoting/host/desktop_session_win.h @@ -0,0 +1,77 @@ +// 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. + +#ifndef REMOTING_HOST_DESKTOP_SESSION_WIN_H_ +#define REMOTING_HOST_DESKTOP_SESSION_WIN_H_ + +#include "base/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "remoting/host/desktop_session.h" +#include "remoting/host/win/wts_console_observer.h" +#include "remoting/host/worker_process_ipc_delegate.h" + +namespace base { +class SingleThreadTaskRunner; +} // namespace base + +namespace remoting { + +class DaemonProcess; +class WorkerProcessLauncher; +class WtsConsoleMonitor; + +// DesktopSession implementation which attaches to the host's physical console. +// Receives IPC messages from the desktop process, running in the console +// session, via |WorkerProcessIpcDelegate|, and monitors console session +// attach/detach events via |WtsConsoleObserer|. +// TODO(alexeypa): replace |WtsConsoleObserver| with an interface capable of +// monitoring both the console and RDP connections. See http://crbug.com/137696. +class DesktopSessionWin + : public DesktopSession, + public WorkerProcessIpcDelegate, + public WtsConsoleObserver { + public: + // Passes the owning |daemon_process|, a unique identifier of the desktop + // session |id| and the interface for monitoring console session attach/detach + // events. Both |daemon_process| and |monitor| must outlive |this|. + DesktopSessionWin( + scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, + DaemonProcess* daemon_process, + int id, + WtsConsoleMonitor* monitor); + virtual ~DesktopSessionWin(); + + // WorkerProcessIpcDelegate implementation. + virtual void OnChannelConnected() OVERRIDE; + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + virtual void OnPermanentError() OVERRIDE; + + // WtsConsoleObserver implementation. + virtual void OnSessionAttached(uint32 session_id) OVERRIDE; + virtual void OnSessionDetached() OVERRIDE; + + private: + // Task runner on which public methods of this class should be called. + scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; + + // Message loop used by the IPC channel. + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; + + // Contains the full path to the desktop binary. + FilePath desktop_binary_; + + // Launches and monitors the desktop process. + scoped_ptr<WorkerProcessLauncher> launcher_; + + // Pointer used to unsubscribe from session attach and detach events. + WtsConsoleMonitor* monitor_; + + DISALLOW_COPY_AND_ASSIGN(DesktopSessionWin); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_DESKTOP_SESSION_WIN_H_ diff --git a/remoting/host/remoting_me2me_host.cc b/remoting/host/remoting_me2me_host.cc index 733b85f..b3b9fe0 100644 --- a/remoting/host/remoting_me2me_host.cc +++ b/remoting/host/remoting_me2me_host.cc @@ -298,9 +298,9 @@ class HostProcess #if defined(REMOTING_MULTI_PROCESS) bool handled = true; IPC_BEGIN_MESSAGE_MAP(HostProcess, message) - IPC_MESSAGE_HANDLER(ChromotingDaemonNetworkMsg_Configuration, - OnConfigUpdated) - IPC_MESSAGE_UNHANDLED(handled = false) + IPC_MESSAGE_HANDLER(ChromotingDaemonNetworkMsg_Configuration, + OnConfigUpdated) + IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; #else // !defined(REMOTING_MULTI_PROCESS) diff --git a/remoting/host/win/host_service.cc b/remoting/host/win/host_service.cc index d8ff0d1..5de9c0f 100644 --- a/remoting/host/win/host_service.cc +++ b/remoting/host/win/host_service.cc @@ -114,6 +114,8 @@ void HostService::AddWtsConsoleObserver(WtsConsoleObserver* observer) { DCHECK(main_task_runner_->BelongsToCurrentThread()); console_observers_.AddObserver(observer); + if (console_session_id_ != kInvalidSessionId) + observer->OnSessionAttached(console_session_id_); } void HostService::RemoveWtsConsoleObserver(WtsConsoleObserver* observer) { @@ -128,6 +130,8 @@ void HostService::OnChildStopped() { } void HostService::OnSessionChange() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + // WTSGetActiveConsoleSessionId is a very cheap API. It basically reads // a single value from shared memory. Therefore it is better to check if // the console session is still the same every time a session change diff --git a/remoting/host/win/worker_process_launcher.cc b/remoting/host/win/worker_process_launcher.cc index e8b698a..63840a6 100644 --- a/remoting/host/win/worker_process_launcher.cc +++ b/remoting/host/win/worker_process_launcher.cc @@ -186,7 +186,11 @@ void WorkerProcessLauncher::Core::Stop() { void WorkerProcessLauncher::Core::Send(IPC::Message* message) { DCHECK(caller_task_runner_->BelongsToCurrentThread()); - launcher_delegate_->Send(message); + if (ipc_enabled_) { + launcher_delegate_->Send(message); + } else { + delete message; + } } void WorkerProcessLauncher::Core::OnObjectSignaled(HANDLE object) { diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index 6e3365e..9fbb51a 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -626,6 +626,10 @@ 'host/daemon_process.cc', 'host/daemon_process.h', 'host/daemon_process_win.cc', + 'host/desktop_session.cc', + 'host/desktop_session.h', + 'host/desktop_session_win.cc', + 'host/desktop_session_win.h', 'host/host_exit_codes.h', 'host/ipc_consts.cc', 'host/ipc_consts.h', @@ -1426,6 +1430,7 @@ 'remoting_protocol', 'differ_block', '../crypto/crypto.gyp:crypto', + '../ipc/ipc.gyp:ipc', ], 'defines': [ 'VERSION=<(version_full)', @@ -1446,6 +1451,8 @@ 'host/chromoting_host.h', 'host/chromoting_host_context.cc', 'host/chromoting_host_context.h', + 'host/chromoting_messages.cc', + 'host/chromoting_messages.h', 'host/client_session.cc', 'host/client_session.h', 'host/clipboard.h', @@ -1626,12 +1633,6 @@ ], }, }], - ['OS=="win"', { - 'sources': [ - 'host/chromoting_messages.cc', - 'host/chromoting_messages.h', - ], - }], ], }, # end of target 'remoting_host' @@ -1700,7 +1701,6 @@ '../base/base.gyp:base', '../base/base.gyp:base_i18n', '../google_apis/google_apis.gyp:google_apis', - '../ipc/ipc.gyp:ipc', '../media/media.gyp:media', '../net/net.gyp:net', ], @@ -2031,9 +2031,18 @@ 'codec/video_encoder_row_based_unittest.cc', 'codec/video_encoder_vp8_unittest.cc', 'host/audio_capturer_win_unittest.cc', + 'host/branding.cc', + 'host/branding.h', 'host/chromoting_host_context_unittest.cc', 'host/chromoting_host_unittest.cc', 'host/client_session_unittest.cc', + 'host/config_file_watcher.cc', + 'host/config_file_watcher.h', + 'host/daemon_process.cc', + 'host/daemon_process.h', + 'host/daemon_process_unittest.cc', + 'host/desktop_session.cc', + 'host/desktop_session.h', 'host/differ_block_unittest.cc', 'host/differ_unittest.cc', 'host/heartbeat_sender_unittest.cc', @@ -2101,9 +2110,6 @@ ], 'conditions': [ [ 'OS=="win"', { - 'dependencies': [ - '../ipc/ipc.gyp:ipc', - ], 'include_dirs': [ '../breakpad/src', ], |