diff options
author | alexeypa@chromium.org <alexeypa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-02-23 19:07:44 +0000 |
---|---|---|
committer | alexeypa@chromium.org <alexeypa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-02-23 19:07:44 +0000 |
commit | 4447016b31992cea934c3f83fb5d0ddd4147f56e (patch) | |
tree | dd986da8e5ac0fae1fd34f9a0227d126c160fb30 /remoting | |
parent | 98b5eef7d47215a318634daa9cbc411dae2ecf71 (diff) | |
download | chromium_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.cc | 395 | ||||
-rw-r--r-- | remoting/host/win/rdp_client.h | 54 | ||||
-rw-r--r-- | remoting/host/win/rdp_client_unittest.cc | 143 | ||||
-rw-r--r-- | remoting/host/win/rdp_client_window.cc | 238 | ||||
-rw-r--r-- | remoting/host/win/rdp_client_window.h | 133 | ||||
-rw-r--r-- | remoting/remoting.gyp | 20 |
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', ], |