summaryrefslogtreecommitdiffstats
path: root/remoting
diff options
context:
space:
mode:
authoralexeypa@chromium.org <alexeypa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-02-23 19:07:44 +0000
committeralexeypa@chromium.org <alexeypa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-02-23 19:07:44 +0000
commit4447016b31992cea934c3f83fb5d0ddd4147f56e (patch)
treedd986da8e5ac0fae1fd34f9a0227d126c160fb30 /remoting
parent98b5eef7d47215a318634daa9cbc411dae2ecf71 (diff)
downloadchromium_src-4447016b31992cea934c3f83fb5d0ddd4147f56e.zip
chromium_src-4447016b31992cea934c3f83fb5d0ddd4147f56e.tar.gz
chromium_src-4447016b31992cea934c3f83fb5d0ddd4147f56e.tar.bz2
Introducing RdpConsole class that uses the MS RDP ActiveX control to create a Windows session.
BUG=137696,177832 TEST=remoting_unittests.RdpConsoleTest Review URL: https://chromiumcodereview.appspot.com/12320045 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@184323 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
-rw-r--r--remoting/host/win/rdp_client.cc395
-rw-r--r--remoting/host/win/rdp_client.h54
-rw-r--r--remoting/host/win/rdp_client_unittest.cc143
-rw-r--r--remoting/host/win/rdp_client_window.cc238
-rw-r--r--remoting/host/win/rdp_client_window.h133
-rw-r--r--remoting/remoting.gyp20
6 files changed, 983 insertions, 0 deletions
diff --git a/remoting/host/win/rdp_client.cc b/remoting/host/win/rdp_client.cc
new file mode 100644
index 0000000..2f1a134
--- /dev/null
+++ b/remoting/host/win/rdp_client.cc
@@ -0,0 +1,395 @@
+// Copyright (c) 2013 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/win/rdp_client.h"
+
+#include <windows.h>
+#include <iphlpapi.h>
+#include <winsock2.h>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/single_thread_task_runner.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_util.h"
+#include "remoting/base/typed_buffer.h"
+#include "remoting/host/win/rdp_client_window.h"
+
+namespace remoting {
+
+namespace {
+
+// Default width and hight of the RDP client window.
+const long kDefaultWidth = 1024;
+const long kDefaultHeight = 768;
+
+// The range of addresses RdpClient may use to distinguish client connections:
+// 127.0.0.2 - 127.255.255.254. 127.0.0.1 is explicitly blocked by the RDP
+// ActiveX control.
+const int kMinLoopbackAddress = 0x7f000002;
+const int kMaxLoopbackAddress = 0x7ffffffe;
+
+const int kRdpPort = 3389;
+
+} // namespace
+
+// The core of RdpClient is ref-counted since it services calls and notifies
+// events on the caller task runner, but runs the ActiveX control on the UI
+// task runner.
+class RdpClient::Core
+ : public base::RefCountedThreadSafe<Core>,
+ public RdpClientWindow::EventHandler {
+ public:
+ Core(
+ scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
+ RdpClient::EventHandler* event_handler);
+
+ // Initiates a loopback RDP connection.
+ void Connect();
+
+ // Initiates a graceful shutdown of the RDP connection.
+ void Disconnect();
+
+ // RdpClientWindow::EventHandler interface.
+ virtual void OnConnected() OVERRIDE;
+ virtual void OnDisconnected() OVERRIDE;
+
+ private:
+ friend class base::RefCountedThreadSafe<Core>;
+ virtual ~Core();
+
+ // Returns the local address that is connected to |remote_endpoint|.
+ // The address is returned in |*local_endpoint|. The method fails if
+ // the connection could not be found in the table of TCP connections or there
+ // are more than one matching connection.
+ bool MatchRemoteEndpoint(const sockaddr_in& remote_endpoint,
+ sockaddr_in* local_endpoint);
+
+ // Same as MatchRemoteEndpoint() but also uses the PID of the process bound
+ // to |local_endpoint| to match the connection.
+ bool MatchRemoteEndpointWithPid(const sockaddr_in& remote_endpoint,
+ DWORD local_pid,
+ sockaddr_in* local_endpoint);
+
+ // Helpers for the event handler's methods that make sure that OnRdpClosed()
+ // is the last notification delivered and is delevered only once.
+ void NotifyConnected(const net::IPEndPoint& client_endpoint);
+ void NotifyClosed();
+
+ // Task runner on which the caller expects |event_handler_| to be notified.
+ scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_;
+
+ // Task runner on which |rdp_client_window_| is running.
+ scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;
+
+ // Event handler receiving notification about connection state. The pointer is
+ // cleared when Disconnect() methods is called, stopping any further updates.
+ RdpClient::EventHandler* event_handler_;
+
+ // Points to GetExtendedTcpTable().
+ typedef DWORD (WINAPI * GetExtendedTcpTableFn)(
+ PVOID, PDWORD, BOOL, ULONG, TCP_TABLE_CLASS, ULONG);
+ GetExtendedTcpTableFn get_extended_tcp_table_;
+
+ // Hosts the RDP ActiveX control.
+ scoped_ptr<RdpClientWindow> rdp_client_window_;
+
+ // The endpoint that |rdp_client_window_| connects to.
+ sockaddr_in server_address_;
+
+ // Same as |server_address_| but represented as net::IPEndPoint.
+ net::IPEndPoint server_endpoint_;
+
+ // A self-reference to keep the object alive during connection shutdown.
+ scoped_refptr<Core> self_;
+
+ DISALLOW_COPY_AND_ASSIGN(Core);
+};
+
+RdpClient::RdpClient(
+ scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
+ EventHandler* event_handler) {
+ DCHECK(caller_task_runner->BelongsToCurrentThread());
+
+ core_ = new Core(caller_task_runner, ui_task_runner, event_handler);
+ core_->Connect();
+}
+
+RdpClient::~RdpClient() {
+ DCHECK(CalledOnValidThread());
+
+ core_->Disconnect();
+}
+
+RdpClient::Core::Core(
+ scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
+ RdpClient::EventHandler* event_handler)
+ : caller_task_runner_(caller_task_runner),
+ ui_task_runner_(ui_task_runner),
+ event_handler_(event_handler),
+ get_extended_tcp_table_(NULL) {
+}
+
+void RdpClient::Core::Connect() {
+ if (!ui_task_runner_->BelongsToCurrentThread()) {
+ ui_task_runner_->PostTask(FROM_HERE, base::Bind(&Core::Connect, this));
+ return;
+ }
+
+ DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI);
+ DCHECK(!rdp_client_window_);
+ DCHECK(!self_);
+
+ // This code link statically agains iphlpapi.dll, so it must be loaded
+ // already.
+ HMODULE iphlpapi_handle = GetModuleHandle(L"iphlpapi.dll");
+ CHECK(iphlpapi_handle != NULL);
+
+ // Get a pointer to GetExtendedTcpTable() which is available starting from
+ // XP SP2 / W2K3 SP1.
+ get_extended_tcp_table_ = reinterpret_cast<GetExtendedTcpTableFn>(
+ GetProcAddress(iphlpapi_handle, "GetExtendedTcpTable"));
+ CHECK(get_extended_tcp_table_);
+
+ // Generate a random loopback address to connect to.
+ memset(&server_address_, 0, sizeof(server_address_));
+ server_address_.sin_family = AF_INET;
+ server_address_.sin_port = htons(kRdpPort);
+ server_address_.sin_addr.S_un.S_addr = htonl(
+ base::RandInt(kMinLoopbackAddress, kMaxLoopbackAddress));
+
+ CHECK(server_endpoint_.FromSockAddr(
+ reinterpret_cast<struct sockaddr*>(&server_address_),
+ sizeof(server_address_)));
+
+ // Create the ActiveX control window.
+ rdp_client_window_.reset(new RdpClientWindow(server_endpoint_, this));
+ if (!rdp_client_window_->Connect(SkISize::Make(kDefaultWidth,
+ kDefaultHeight))) {
+ rdp_client_window_.reset();
+
+ // Notify the caller that connection attempt failed.
+ NotifyClosed();
+ }
+}
+
+void RdpClient::Core::Disconnect() {
+ if (!ui_task_runner_->BelongsToCurrentThread()) {
+ ui_task_runner_->PostTask(FROM_HERE, base::Bind(&Core::Disconnect, this));
+ return;
+ }
+
+ // The caller does not expect any notifications to be delivered after this
+ // point.
+ event_handler_ = NULL;
+
+ // Gracefully shutdown the RDP connection.
+ if (rdp_client_window_) {
+ self_ = this;
+ rdp_client_window_->Disconnect();
+ }
+}
+
+void RdpClient::Core::OnConnected() {
+ DCHECK(ui_task_runner_->BelongsToCurrentThread());
+ DCHECK(rdp_client_window_);
+
+ // Now that the connection is established, map the server endpoint to
+ // the client one.
+ bool result;
+ sockaddr_in client_address;
+ if (get_extended_tcp_table_) {
+ result = MatchRemoteEndpointWithPid(server_address_, GetCurrentProcessId(),
+ &client_address);
+ } else {
+ result = MatchRemoteEndpoint(server_address_, &client_address);
+ }
+
+ if (!result) {
+ NotifyClosed();
+ Disconnect();
+ return;
+ }
+
+ net::IPEndPoint client_endpoint;
+ CHECK(client_endpoint.FromSockAddr(
+ reinterpret_cast<struct sockaddr*>(&client_address),
+ sizeof(client_address)));
+
+ NotifyConnected(client_endpoint);
+}
+
+void RdpClient::Core::OnDisconnected() {
+ DCHECK(ui_task_runner_->BelongsToCurrentThread());
+ DCHECK(rdp_client_window_);
+
+ NotifyClosed();
+
+ // Delay window destruction until no ActiveX control's code is on the stack.
+ ui_task_runner_->DeleteSoon(FROM_HERE, rdp_client_window_.release());
+ self_ = NULL;
+}
+
+RdpClient::Core::~Core() {
+ DCHECK(!event_handler_);
+ DCHECK(!rdp_client_window_);
+}
+
+bool RdpClient::Core::MatchRemoteEndpoint(
+ const sockaddr_in& remote_endpoint,
+ sockaddr_in* local_endpoint) {
+ TypedBuffer<MIB_TCPTABLE> tcp_table;
+ DWORD tcp_table_size = 0;
+
+ // Retrieve the size of the buffer needed for the IPv4 TCP connection table.
+ DWORD result = GetTcpTable(tcp_table.get(), &tcp_table_size, FALSE);
+ for (int retries = 0; retries < 5 && result == ERROR_INSUFFICIENT_BUFFER;
+ ++retries) {
+ // Allocate a buffer that is large enough.
+ TypedBuffer<MIB_TCPTABLE> buffer(tcp_table_size);
+ tcp_table.Swap(buffer);
+
+ // Get the list of TCP connections (IPv4 only).
+ result = GetTcpTable(tcp_table.get(), &tcp_table_size, FALSE);
+ }
+ if (result != ERROR_SUCCESS) {
+ SetLastError(result);
+ LOG_GETLASTERROR(ERROR)
+ << "Failed to get the list of existing IPv4 TCP endpoints";
+ return false;
+ }
+
+ // Match the connection by the server endpoint.
+ bool found = false;
+ MIB_TCPROW* row = tcp_table->table;
+ MIB_TCPROW* row_end = row + tcp_table->dwNumEntries;
+ for (; row != row_end; ++row) {
+ if (row->dwRemoteAddr != remote_endpoint.sin_addr.S_un.S_addr ||
+ LOWORD(row->dwRemotePort) != remote_endpoint.sin_port) {
+ continue;
+ }
+
+ // Check if more than one connection has been matched.
+ if (found) {
+ LOG(ERROR) << "More than one connections matching "
+ << server_endpoint_.ToString() << " found.";
+ return false;
+ }
+
+ memset(local_endpoint, 0, sizeof(*local_endpoint));
+ local_endpoint->sin_family = AF_INET;
+ local_endpoint->sin_addr.S_un.S_addr = row->dwLocalAddr;
+ local_endpoint->sin_port = LOWORD(row->dwLocalPort);
+ found = true;
+ }
+
+ if (!found) {
+ LOG(ERROR) << "No connection matching " << server_endpoint_.ToString()
+ << " found.";
+ return false;
+ }
+
+ return true;
+}
+
+bool RdpClient::Core::MatchRemoteEndpointWithPid(
+ const sockaddr_in& remote_endpoint,
+ DWORD local_pid,
+ sockaddr_in* local_endpoint) {
+ TypedBuffer<MIB_TCPTABLE_OWNER_PID> tcp_table;
+ DWORD tcp_table_size = 0;
+
+ // Retrieve the size of the buffer needed for the IPv4 TCP connection table.
+ DWORD result = get_extended_tcp_table_(tcp_table.get(),
+ &tcp_table_size,
+ FALSE,
+ AF_INET,
+ TCP_TABLE_OWNER_PID_CONNECTIONS,
+ 0);
+ for (int retries = 0; retries < 5 && result == ERROR_INSUFFICIENT_BUFFER;
+ ++retries) {
+ // Allocate a buffer that is large enough.
+ TypedBuffer<MIB_TCPTABLE_OWNER_PID> buffer(tcp_table_size);
+ tcp_table.Swap(buffer);
+
+ // Get the list of TCP connections (IPv4 only).
+ result = get_extended_tcp_table_(tcp_table.get(),
+ &tcp_table_size,
+ FALSE,
+ AF_INET,
+ TCP_TABLE_OWNER_PID_CONNECTIONS,
+ 0);
+ }
+ if (result != ERROR_SUCCESS) {
+ SetLastError(result);
+ LOG_GETLASTERROR(ERROR)
+ << "Failed to get the list of existing IPv4 TCP endpoints";
+ return false;
+ }
+
+ // Match the connection by the server endpoint.
+ bool found = false;
+ MIB_TCPROW_OWNER_PID* row = tcp_table->table;
+ MIB_TCPROW_OWNER_PID* row_end = row + tcp_table->dwNumEntries;
+ for (; row != row_end; ++row) {
+ if (row->dwRemoteAddr != remote_endpoint.sin_addr.S_un.S_addr ||
+ LOWORD(row->dwRemotePort) != remote_endpoint.sin_port ||
+ row->dwOwningPid != local_pid) {
+ continue;
+ }
+
+ // Check if more than one connection has been matched.
+ if (found) {
+ LOG(ERROR) << "More than one connections matching "
+ << server_endpoint_.ToString() << " found.";
+ return false;
+ }
+
+ memset(local_endpoint, 0, sizeof(*local_endpoint));
+ local_endpoint->sin_family = AF_INET;
+ local_endpoint->sin_addr.S_un.S_addr = row->dwLocalAddr;
+ local_endpoint->sin_port = LOWORD(row->dwLocalPort);
+ found = true;
+ }
+
+ if (!found) {
+ LOG(ERROR) << "No connection matching " << server_endpoint_.ToString()
+ << " and PID " << local_pid << " found.";
+ return false;
+ }
+
+ return true;
+}
+
+void RdpClient::Core::NotifyConnected(const net::IPEndPoint& client_endpoint) {
+ if (!caller_task_runner_->BelongsToCurrentThread()) {
+ caller_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Core::NotifyConnected, this, client_endpoint));
+ return;
+ }
+
+ if (event_handler_)
+ event_handler_->OnRdpConnected(client_endpoint);
+}
+
+void RdpClient::Core::NotifyClosed() {
+ if (!caller_task_runner_->BelongsToCurrentThread()) {
+ caller_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Core::NotifyClosed, this));
+ return;
+ }
+
+ if (event_handler_) {
+ RdpClient::EventHandler* event_handler = event_handler_;
+ event_handler_ = NULL;
+ event_handler->OnRdpClosed();
+ }
+}
+
+} // namespace remoting
diff --git a/remoting/host/win/rdp_client.h b/remoting/host/win/rdp_client.h
new file mode 100644
index 0000000..4987397
--- /dev/null
+++ b/remoting/host/win/rdp_client.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2013 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_WIN_RDP_CLIENT_H_
+#define REMOTING_HOST_WIN_RDP_CLIENT_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/non_thread_safe.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+} // namespace
+
+namespace net {
+class IPEndPoint;
+} // namespace
+
+namespace remoting {
+
+// Establishes a loopback RDP connection to spawn a new Windows session.
+class RdpClient : public base::NonThreadSafe {
+ public:
+ class EventHandler {
+ public:
+ virtual ~EventHandler() {}
+
+ // Notifies the event handler that an RDP connection has been established
+ // successfully.
+ virtual void OnRdpConnected(const net::IPEndPoint& client_endpoint) = 0;
+
+ // Notifies that the RDP connection has been closed.
+ virtual void OnRdpClosed() = 0;
+ };
+
+ RdpClient(
+ scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
+ EventHandler* event_handler);
+ virtual ~RdpClient();
+
+ private:
+ // The actual implementation resides in Core class.
+ class Core;
+ scoped_refptr<Core> core_;
+
+ DISALLOW_COPY_AND_ASSIGN(RdpClient);
+};
+
+} // namespace remoting
+
+#endif // REMOTING_HOST_WIN_RDP_CLIENT_H_
diff --git a/remoting/host/win/rdp_client_unittest.cc b/remoting/host/win/rdp_client_unittest.cc
new file mode 100644
index 0000000..38f59cd
--- /dev/null
+++ b/remoting/host/win/rdp_client_unittest.cc
@@ -0,0 +1,143 @@
+// Copyright (c) 2013 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.
+
+// ATL headers have to go first.
+#include <atlbase.h>
+#include <atlhost.h>
+
+#include "base/basictypes.h"
+#include "base/message_loop.h"
+#include "base/run_loop.h"
+#include "base/win/scoped_com_initializer.h"
+#include "net/base/ip_endpoint.h"
+#include "remoting/base/auto_thread_task_runner.h"
+#include "remoting/host/win/rdp_client.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gmock_mutant.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::AtMost;
+using testing::InvokeWithoutArgs;
+
+namespace remoting {
+
+namespace {
+
+class MockRdpClientEventHandler : public RdpClient::EventHandler {
+ public:
+ MockRdpClientEventHandler() {}
+ virtual ~MockRdpClientEventHandler() {}
+
+ MOCK_METHOD1(OnRdpConnected, void(const net::IPEndPoint&));
+ MOCK_METHOD0(OnRdpClosed, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockRdpClientEventHandler);
+};
+
+// a14498c6-7f3b-4e42-9605-6c4a20d53c87
+static GUID RdpClientModuleLibid = {
+ 0xa14498c6,
+ 0x7f3b,
+ 0x4e42,
+ { 0x96, 0x05, 0x6c, 0x4a, 0x20, 0xd5, 0x3c, 0x87 }
+};
+
+class RdpClientModule : public ATL::CAtlModuleT<RdpClientModule> {
+ public:
+ RdpClientModule();
+ virtual ~RdpClientModule();
+
+ DECLARE_LIBID(RdpClientModuleLibid)
+
+ private:
+ base::win::ScopedCOMInitializer com_initializer_;
+};
+
+RdpClientModule::RdpClientModule() {
+ AtlAxWinInit();
+}
+
+RdpClientModule::~RdpClientModule() {
+ AtlAxWinTerm();
+ ATL::_pAtlModule = NULL;
+}
+
+} // namespace
+
+class RdpClientTest : public testing::Test {
+ public:
+ RdpClientTest();
+ virtual ~RdpClientTest();
+
+ virtual void SetUp() OVERRIDE;
+ virtual void TearDown() OVERRIDE;
+
+ // Tears down |rdp_client_|.
+ void CloseRdpClient();
+
+ protected:
+ // The ATL module instance required by the ATL code.
+ scoped_ptr<RdpClientModule> module_;
+
+ // The UI message loop used by RdpClient. The loop is stopped once there is no
+ // more references to |task_runner_|.
+ MessageLoop message_loop_;
+ base::RunLoop run_loop_;
+ scoped_refptr<AutoThreadTaskRunner> task_runner_;
+
+ // Mocks RdpClient::EventHandler for testing.
+ MockRdpClientEventHandler event_handler_;
+
+ // Points to the object being tested.
+ scoped_ptr<RdpClient> rdp_client_;
+};
+
+RdpClientTest::RdpClientTest() : message_loop_(MessageLoop::TYPE_UI) {
+}
+
+RdpClientTest::~RdpClientTest() {
+}
+
+void RdpClientTest::SetUp() {
+ // Arrange to run |message_loop_| until no components depend on it.
+ task_runner_ = new AutoThreadTaskRunner(
+ message_loop_.message_loop_proxy(), run_loop_.QuitClosure());
+
+ module_.reset(new RdpClientModule());
+}
+
+void RdpClientTest::TearDown() {
+ EXPECT_TRUE(!rdp_client_);
+
+ module_.reset();
+}
+
+void RdpClientTest::CloseRdpClient() {
+ EXPECT_TRUE(rdp_client_);
+
+ rdp_client_.reset();
+}
+
+// Creates a loopback RDP connection.
+TEST_F(RdpClientTest, Basic) {
+ // An ability to establish a loopback RDP connection depends on many factors
+ // including OS SKU and having RDP enabled. Accept both successful connection
+ // and a connection error as a successful outcome.
+ EXPECT_CALL(event_handler_, OnRdpConnected(_))
+ .Times(AtMost(1))
+ .WillOnce(InvokeWithoutArgs(this, &RdpClientTest::CloseRdpClient));
+ EXPECT_CALL(event_handler_, OnRdpClosed())
+ .Times(AtMost(1))
+ .WillOnce(InvokeWithoutArgs(this, &RdpClientTest::CloseRdpClient));
+
+ rdp_client_.reset(new RdpClient(task_runner_, task_runner_,
+ &event_handler_));
+ task_runner_ = NULL;
+
+ run_loop_.Run();
+}
+
+} // namespace remoting
diff --git a/remoting/host/win/rdp_client_window.cc b/remoting/host/win/rdp_client_window.cc
new file mode 100644
index 0000000..4e46d4f
--- /dev/null
+++ b/remoting/host/win/rdp_client_window.cc
@@ -0,0 +1,238 @@
+// Copyright (c) 2013 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/win/rdp_client_window.h"
+
+#include "base/logging.h"
+#include "base/utf_string_conversions.h"
+#include "base/win/scoped_bstr.h"
+
+namespace remoting {
+
+RdpClientWindow::RdpClientWindow(const net::IPEndPoint& server_endpoint,
+ EventHandler* event_handler)
+ : event_handler_(event_handler),
+ server_endpoint_(server_endpoint) {
+}
+
+RdpClientWindow::~RdpClientWindow() {
+ if (m_hWnd)
+ DestroyWindow();
+
+ DCHECK(!client_);
+ DCHECK(!client_settings_);
+}
+
+bool RdpClientWindow::Connect(const SkISize& screen_size) {
+ DCHECK(!m_hWnd);
+
+ RECT rect = { 0, 0, screen_size.width(), screen_size.height() };
+ bool result = Create(NULL, rect, NULL) != NULL;
+
+ // Hide the window since this class is about establishing a connection, not
+ // about showing a UI to the user.
+ if (result)
+ ShowWindow(SW_HIDE);
+
+ return result;
+}
+
+void RdpClientWindow::Disconnect() {
+ DCHECK(m_hWnd);
+
+ SendMessage(WM_CLOSE);
+}
+
+void RdpClientWindow::OnClose() {
+ if (!client_) {
+ NotifyDisconnected();
+ return;
+ }
+
+ // Request a graceful shutdown.
+ mstsc::ControlCloseStatus close_status;
+ HRESULT result = client_->RequestClose(&close_status);
+ if (FAILED(result)) {
+ LOG(ERROR) << "Failed to request a graceful shutdown of an RDP connection"
+ << ", result=0x" << std::hex << result << std::dec;
+ NotifyDisconnected();
+ return;
+ }
+
+ if (close_status != mstsc::controlCloseWaitForEvents) {
+ NotifyDisconnected();
+ return;
+ }
+
+ // Expect IMsTscAxEvents::OnConfirmClose() or IMsTscAxEvents::OnDisconnect()
+ // to be called if mstsc::controlCloseWaitForEvents was returned.
+}
+
+LRESULT RdpClientWindow::OnCreate(CREATESTRUCT* create_struct) {
+ CAxWindow2 activex_window;
+ base::win::ScopedComPtr<IUnknown> control;
+ HRESULT result = E_FAIL;
+ base::win::ScopedBstr server_name(
+ UTF8ToUTF16(server_endpoint_.ToStringWithoutPort()).c_str());
+
+ RECT rect;
+ if (!GetClientRect(&rect)) {
+ result = HRESULT_FROM_WIN32(GetLastError());
+ goto done;
+ }
+
+ // Create the child window that actually hosts the ActiveX control.
+ activex_window.Create(m_hWnd, rect, NULL, WS_CHILD | WS_VISIBLE | WS_BORDER);
+ if (activex_window.m_hWnd == NULL) {
+ result = HRESULT_FROM_WIN32(GetLastError());
+ goto done;
+ }
+
+ // Instantiate the RDP ActiveX control.
+ result = activex_window.CreateControlEx(
+ OLESTR("MsTscAx.MsTscAx"),
+ NULL,
+ NULL,
+ control.Receive(),
+ __uuidof(mstsc::IMsTscAxEvents),
+ reinterpret_cast<IUnknown*>(static_cast<RdpEventsSink*>(this)));
+ if (FAILED(result))
+ goto done;
+
+ result = control.QueryInterface(client_.Receive());
+ if (FAILED(result))
+ goto done;
+
+ // Set the server name to connect to.
+ result = client_->put_Server(server_name);
+ if (FAILED(result))
+ goto done;
+
+ // Fetch IMsRdpClientAdvancedSettings interface for the client.
+ result = client_->get_AdvancedSettings2(client_settings_.Receive());
+ if (FAILED(result))
+ goto done;
+
+ // Disable background input mode.
+ result = client_settings_->put_allowBackgroundInput(0);
+ if (FAILED(result))
+ goto done;
+
+ // Do not use bitmap cache.
+ result = client_settings_->put_BitmapPersistence(0);
+ if (SUCCEEDED(result))
+ result = client_settings_->put_CachePersistenceActive(0);
+ if (FAILED(result))
+ goto done;
+
+ // Do not use compression.
+ result = client_settings_->put_Compress(0);
+ if (FAILED(result))
+ goto done;
+
+ // Disable printer and clipboard redirection.
+ result = client_settings_->put_DisableRdpdr(FALSE);
+ if (FAILED(result))
+ goto done;
+
+ // Do not display the connection bar.
+ result = client_settings_->put_DisplayConnectionBar(VARIANT_FALSE);
+ if (FAILED(result))
+ goto done;
+
+ // Do not grab focus on connect.
+ result = client_settings_->put_GrabFocusOnConnect(VARIANT_FALSE);
+ if (FAILED(result))
+ goto done;
+
+ // Set the port to connect to.
+ result = client_settings_->put_RDPPort(server_endpoint_.port());
+ if (FAILED(result))
+ goto done;
+
+ result = client_->Connect();
+ if (FAILED(result))
+ goto done;
+
+done:
+ if (FAILED(result)) {
+ LOG(ERROR) << "Failed to start an RDP connection: error=" << std::hex
+ << result << std::dec;
+ client_.Release();
+ client_settings_.Release();
+ return -1;
+ }
+
+ return 0;
+}
+
+void RdpClientWindow::OnDestroy() {
+ client_.Release();
+ client_settings_.Release();
+}
+
+HRESULT RdpClientWindow::OnConnected() {
+ VLOG(3) << "The RDP client control has established a connection";
+
+ NotifyConnected();
+ return S_OK;
+}
+
+HRESULT RdpClientWindow::OnDisconnected(long reason) {
+ // Log the disconnect reason and extended code.
+ mstsc::ExtendedDisconnectReasonCode extended_code;
+ HRESULT result = client_->get_ExtendedDisconnectReason(&extended_code);
+ if (FAILED(result))
+ extended_code = mstsc::exDiscReasonNoInfo;
+
+ VLOG(1) << "The RDP client control has been disconnected: reason=" << reason
+ << ", extended_code=" << extended_code;
+
+ // Try to log the error message as well.
+ if (extended_code != mstsc::exDiscReasonNoInfo) {
+ base::win::ScopedComPtr<mstsc::IMsRdpClient5> client5;
+ result = client_.QueryInterface(client5.Receive());
+ if (SUCCEEDED(result)) {
+ base::win::ScopedBstr error_message;
+ reason = client5->GetErrorDescription(reason, extended_code,
+ error_message.Receive());
+ if (SUCCEEDED(result)) {
+ VLOG(1) << " error_message=" << error_message;
+ }
+ }
+ }
+
+ NotifyDisconnected();
+ return S_OK;
+}
+
+HRESULT RdpClientWindow::OnFatalError(long error_code) {
+ LOG(ERROR) << "An error occured in the RDP client control: error_code="
+ << error_code;
+
+ NotifyDisconnected();
+ return S_OK;
+}
+
+HRESULT RdpClientWindow::OnConfirmClose(VARIANT_BOOL* allow_close) {
+ *allow_close = VARIANT_TRUE;
+
+ NotifyDisconnected();
+ return S_OK;
+}
+
+void RdpClientWindow::NotifyConnected() {
+ if (event_handler_)
+ event_handler_->OnConnected();
+}
+
+void RdpClientWindow::NotifyDisconnected() {
+ if (event_handler_) {
+ EventHandler* event_handler = event_handler_;
+ event_handler_ = NULL;
+ event_handler->OnDisconnected();
+ }
+}
+
+} // namespace remoting
diff --git a/remoting/host/win/rdp_client_window.h b/remoting/host/win/rdp_client_window.h
new file mode 100644
index 0000000..0015eaf0
--- /dev/null
+++ b/remoting/host/win/rdp_client_window.h
@@ -0,0 +1,133 @@
+// Copyright (c) 2013 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_WIN_RDP_HOST_WINDOW_H_
+#define REMOTING_HOST_WIN_RDP_HOST_WINDOW_H_
+
+#include <atlbase.h>
+#include <atlcom.h>
+#include <atlcrack.h>
+#include <atlctl.h>
+
+#include "base/basictypes.h"
+#include "base/message_loop.h"
+#include "base/win/scoped_comptr.h"
+#include "net/base/ip_endpoint.h"
+#include "third_party/skia/include/core/SkSize.h"
+
+#import "PROGID:MsTscAx.MsTscAx" \
+ exclude("wireHWND", "_RemotableHandle", "__MIDL_IWinTypes_0009"), \
+ rename_namespace("mstsc") raw_interfaces_only no_implementation
+
+namespace remoting {
+
+// RdpClientWindow is used to establish a connection to the given RDP endpoint.
+// It is a GUI window class that hosts Microsoft RDP ActiveX control, which
+// takes care of handling RDP properly. RdpClientWindow must be used only on
+// a UI thread.
+class RdpClientWindow
+ : public CWindowImpl<RdpClientWindow, CWindow, CFrameWinTraits>,
+ public IDispEventImpl<1, RdpClientWindow,
+ &__uuidof(mstsc::IMsTscAxEvents),
+ &__uuidof(mstsc::__MSTSCLib), 1, 0> {
+ public:
+ // Receives connect/disconnect notifications. The notifications can be
+ // delivered after RdpClientWindow::Connect() returned success.
+ //
+ // RdpClientWindow guarantees that OnDisconnected() is the last notification
+ // the event handler receives. OnDisconnected() is guaranteed to be called
+ // only once.
+ class EventHandler {
+ public:
+ virtual ~EventHandler() {}
+
+ // Invoked when the RDP control has established a connection.
+ virtual void OnConnected() = 0;
+
+ // Invoked when the RDP control has been disconnected from the RDP server.
+ // This includes both graceful shutdown and any fatal error condition.
+ //
+ // Once RdpClientWindow::Connect() returns success the owner of the
+ // |RdpClientWindow| object must keep it alive until OnDisconnected() is
+ // called.
+ //
+ // OnDisconnected() should not delete |RdpClientWindow| object directly.
+ // Instead it should post a task to delete the object. The ActiveX code
+ // expects the window be alive until the currently handled window message is
+ // completely processed.
+ virtual void OnDisconnected() = 0;
+ };
+
+ DECLARE_WND_CLASS(L"RdpClientWindow")
+
+ // Specifies the endpoint to connect to and passes the event handler pointer
+ // to be notified about connection events.
+ RdpClientWindow(const net::IPEndPoint& server_endpoint,
+ EventHandler* event_handler);
+ ~RdpClientWindow();
+
+ // Creates the window along with the ActiveX control and initiates the
+ // connection. |screen_size| specifies resolution of the screen. Returns false
+ // if an error occurs.
+ bool Connect(const SkISize& screen_size);
+
+ // Initiates shutdown of the connection. The caller must not delete |this|
+ // until it receives OnDisconnected() notification.
+ void Disconnect();
+
+ private:
+ typedef IDispEventImpl<1, RdpClientWindow,
+ &__uuidof(mstsc::IMsTscAxEvents),
+ &__uuidof(mstsc::__MSTSCLib), 1, 0> RdpEventsSink;
+
+ // Handled window messages.
+ BEGIN_MSG_MAP_EX(RdpClientWindow)
+ MSG_WM_CLOSE(OnClose)
+ MSG_WM_CREATE(OnCreate)
+ MSG_WM_DESTROY(OnDestroy)
+ END_MSG_MAP()
+
+ // Requests the RDP ActiveX control to close the connection gracefully.
+ void OnClose();
+
+ // Creates the RDP ActiveX control, configures it, and initiates an RDP
+ // connection to |server_endpoint_|.
+ LRESULT OnCreate(CREATESTRUCT* create_struct);
+
+ // Releases the RDP ActiveX control interfaces.
+ void OnDestroy();
+
+ BEGIN_SINK_MAP(RdpClientWindow)
+ SINK_ENTRY_EX(1, __uuidof(mstsc::IMsTscAxEvents), 2, OnConnected)
+ SINK_ENTRY_EX(1, __uuidof(mstsc::IMsTscAxEvents), 4, OnDisconnected)
+ SINK_ENTRY_EX(1, __uuidof(mstsc::IMsTscAxEvents), 10, OnFatalError)
+ SINK_ENTRY_EX(1, __uuidof(mstsc::IMsTscAxEvents), 15, OnConfirmClose)
+ END_SINK_MAP()
+
+ // mstsc::IMsTscAxEvents notifications.
+ STDMETHOD(OnConnected)();
+ STDMETHOD(OnDisconnected)(long reason);
+ STDMETHOD(OnFatalError)(long error_code);
+ STDMETHOD(OnConfirmClose)(VARIANT_BOOL* allow_close);
+
+ // Wrappers for the event handler's methods that make sure that
+ // OnDisconnected() is the last notification delivered and is delevered
+ // only once.
+ void NotifyConnected();
+ void NotifyDisconnected();
+
+ // Invoked to report connect/disconnect events.
+ EventHandler* event_handler_;
+
+ // The endpoint to connect to.
+ net::IPEndPoint server_endpoint_;
+
+ // Interfaces exposed by the RDP ActiveX control.
+ base::win::ScopedComPtr<mstsc::IMsRdpClient> client_;
+ base::win::ScopedComPtr<mstsc::IMsRdpClientAdvancedSettings> client_settings_;
+};
+
+} // namespace remoting
+
+#endif // REMOTING_HOST_WIN_RDP_HOST_WINDOW_H_
diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp
index 0dc2921..cdb3ca9 100644
--- a/remoting/remoting.gyp
+++ b/remoting/remoting.gyp
@@ -432,6 +432,10 @@
'host/win/launch_process_with_token.h',
'host/win/omaha.cc',
'host/win/omaha.h',
+ 'host/win/rdp_client.cc',
+ 'host/win/rdp_client.h',
+ 'host/win/rdp_client_window.cc',
+ 'host/win/rdp_client_window.h',
'host/win/security_descriptor.cc',
'host/win/security_descriptor.h',
'host/win/session_desktop_environment.cc',
@@ -487,11 +491,19 @@
}],
['OS=="win"', {
'defines': [
+ '_ATL_NO_EXCEPTIONS',
'ISOLATION_AWARE_ENABLED=1',
],
'dependencies': [
'../sandbox/sandbox.gyp:sandbox',
],
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ # /MP conflicts with #import directive so we limit the number
+ # of processes to spawn to 1.
+ 'AdditionalOptions': ['/MP1'],
+ },
+ },
}],
],
}, # end of target 'remoting_host'
@@ -1156,6 +1168,9 @@
{
'target_name': 'remoting_configurer',
'type': 'executable',
+ 'defines': [
+ '_ATL_NO_EXCEPTIONS',
+ ],
'dependencies': [
'../base/base.gyp:base',
'../crypto/crypto.gyp:crypto',
@@ -1252,6 +1267,7 @@
'_ATL_APARTMENT_THREADED',
'_ATL_CSTRING_EXPLICIT_CONSTRUCTORS',
'_ATL_NO_AUTOMATIC_NAMESPACE',
+ '_ATL_NO_EXCEPTIONS',
'DAEMON_CONTROLLER_CLSID="{<(daemon_controller_clsid)}"',
'HOST_IMPLEMENTATION',
'ISOLATION_AWARE_ENABLED=1',
@@ -2441,6 +2457,7 @@
'host/setup/pin_validator_unittest.cc',
'host/test_key_pair.h',
'host/video_scheduler_unittest.cc',
+ 'host/win/rdp_client_unittest.cc',
'host/win/worker_process_launcher.cc',
'host/win/worker_process_launcher.h',
'host/win/worker_process_launcher_unittest.cc',
@@ -2480,6 +2497,9 @@
],
'conditions': [
[ 'OS=="win"', {
+ 'defines': [
+ '_ATL_NO_EXCEPTIONS',
+ ],
'include_dirs': [
'../breakpad/src',
],