server: proxy: support static vc passthrough

This commit is contained in:
Kobi Mizrachi 2020-02-02 15:15:28 +02:00 committed by akallabeth
parent 9417350d92
commit 079871ac65
12 changed files with 276 additions and 11 deletions

View File

@ -35,6 +35,9 @@ DisplayControl = TRUE
Clipboard = TRUE
AudioOutput = TRUE
RemoteApp = TRUE
; a list of comma seperated static channels that will be proxied. This feature is useful,
; for example when there's a custom static channel that isn't implemented in freerdp/proxy, and is needed to be proxied when connecting through the proxy.
; Passthrough = ""
[Clipboard]
TextOnly = FALSE

View File

@ -53,6 +53,7 @@ static BOOL demo_plugin_unload()
static proxyPlugin demo_plugin = {
plugin_name, /* name */
plugin_desc, /* description */
demo_plugin_unload, /* PluginUnload */
NULL, /* ClientPreConnect */
NULL, /* ClientLoginFailure */
NULL, /* ServerPostConnect */
@ -60,7 +61,8 @@ static proxyPlugin demo_plugin = {
NULL, /* ServerChannelsFree */
demo_filter_keyboard_event, /* KeyboardEvent */
NULL, /* MouseEvent */
demo_plugin_unload /* PluginUnload */
NULL, /* ClientChannelData */
NULL, /* ServerChannelData */
};
BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager)

View File

@ -44,6 +44,8 @@ typedef struct proxy_plugin
const char* name; /* unique module name */
const char* description; /* module description */
BOOL (*PluginUnload)();
/* proxy hooks. a module can set these function pointers to register hooks */
proxyHookFn ClientPreConnect;
proxyHookFn ClientLoginFailure;
@ -54,8 +56,8 @@ typedef struct proxy_plugin
/* proxy filters. a module can set these function pointers to register filters */
proxyFilterFn KeyboardEvent;
proxyFilterFn MouseEvent;
BOOL (*PluginUnload)();
proxyFilterFn ClientChannelData; /* passthrough channels data */
proxyFilterFn ServerChannelData; /* passthrough channels data */
} proxyPlugin;
/*
@ -95,6 +97,17 @@ typedef struct proxy_mouse_event_info
UINT16 x;
UINT16 y;
} proxyMouseEventInfo;
typedef struct channel_data_event_info
{
/* channel metadata */
const char* channel_name;
UINT16 channel_id;
/* actual data */
const BYTE* data;
int data_len;
} proxyChannelDataEventInfo;
#define WINPR_PACK_POP
#include <winpr/pack.h>

View File

@ -230,6 +230,33 @@ BOOL pf_server_channels_init(pServerContext* ps)
return FALSE;
}
{
/* open static channels for passthrough */
size_t i;
for (i = 0; i < config->PassthroughCount; i++)
{
char* channel_name = config->Passthrough[i];
UINT64 channel_id;
/* only open channel if client joined with it */
if (!WTSVirtualChannelManagerIsChannelJoined(ps->vcm, channel_name))
continue;
ps->vc_handles[i] = WTSVirtualChannelOpen(ps->vcm, WTS_CURRENT_SESSION, channel_name);
if (!ps->vc_handles[i])
{
LOG_ERR(TAG, ps, "WTSVirtualChannelOpen failed for passthrough channel: %s",
channel_name);
return FALSE;
}
channel_id = (UINT64)WTSChannelGetId(ps->context.peer, channel_name);
HashTable_Add(ps->vc_ids, channel_name, (void*)channel_id);
}
}
return pf_modules_run_hook(HOOK_TYPE_SERVER_CHANNELS_INIT, ps->pdata);
}
@ -265,5 +292,13 @@ void pf_server_channels_free(pServerContext* ps)
ps->rail = NULL;
}
{
/* close passthrough channels */
size_t i;
for (i = 0; i < ps->pdata->config->PassthroughCount; i++)
WTSVirtualChannelClose(ps->vc_handles[i]);
}
pf_modules_run_hook(HOOK_TYPE_SERVER_CHANNELS_FREE, ps->pdata);
}

View File

@ -39,6 +39,8 @@
#define TAG PROXY_TAG("client")
static pReceiveChannelData client_receive_channel_data_original = NULL;
static BOOL proxy_server_reactivate(rdpContext* ps, const rdpContext* pc)
{
if (!pf_context_copy_settings(ps->settings, pc->settings))
@ -149,6 +151,37 @@ static BOOL pf_client_pre_connect(freerdp* instance)
*/
LOG_INFO(TAG, pc, "Loading addins");
{
/* add passthrough channels to channel def array */
size_t i;
if (settings->ChannelCount + config->PassthroughCount >= settings->ChannelDefArraySize)
{
LOG_ERR(TAG, pc, "too many channels");
return FALSE;
}
for (i = 0; i < config->PassthroughCount; i++)
{
const char* channel_name = config->Passthrough[i];
CHANNEL_DEF channel = { 0 };
/* only connect connect this channel if already joined in peer connection */
if (!WTSVirtualChannelManagerIsChannelJoined(ps->vcm, channel_name))
{
LOG_INFO(TAG, ps, "client did not connected with channel %s, skipping passthrough",
channel_name);
continue;
}
channel.options = CHANNEL_OPTION_INITIALIZED; /* TODO: Export to config. */
strncpy(channel.name, channel_name, CHANNEL_NAME_LEN);
settings->ChannelDefArray[settings->ChannelCount++] = channel;
}
}
if (!pf_client_load_rdpsnd(pc, config))
{
LOG_ERR(TAG, pc, "Failed to load rdpsnd client");
@ -164,6 +197,41 @@ static BOOL pf_client_pre_connect(freerdp* instance)
return TRUE;
}
static BOOL pf_client_receive_channel_data_hook(freerdp* instance, UINT16 channelId, BYTE* data,
int size, int flags, int totalSize)
{
pClientContext* pc = (pClientContext*)instance->context;
pServerContext* ps = pc->pdata->ps;
proxyData* pdata = ps->pdata;
proxyConfig* config = pdata->config;
size_t i;
const char* channel_name = freerdp_channels_get_name_by_id(instance, channelId);
for (i = 0; i < config->PassthroughCount; i++)
{
if (strncmp(channel_name, config->Passthrough[i], CHANNEL_NAME_LEN) == 0)
{
proxyChannelDataEventInfo ev;
UINT64 server_channel_id;
ev.channel_id = channelId;
ev.channel_name = channel_name;
ev.data = (const BYTE*)data;
ev.data_len = size;
if (!pf_modules_run_filter(FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA, pdata, &ev))
return FALSE;
server_channel_id = (UINT64)HashTable_GetItemValue(ps->vc_ids, (void*)channel_name);
return ps->context.peer->SendChannelData(ps->context.peer, (UINT16)server_channel_id,
data, size);
}
}
return client_receive_channel_data_original(instance, channelId, data, size, flags, totalSize);
}
/**
* Called after a RDP connection was successfully established.
* Settings might have changed during negotiation of client / server feature
@ -224,10 +292,25 @@ static BOOL pf_client_post_connect(freerdp* instance)
pf_client_register_update_callbacks(update);
/* virtual channels receive data hook */
client_receive_channel_data_original = instance->ReceiveChannelData;
instance->ReceiveChannelData = pf_client_receive_channel_data_hook;
/* populate channel name -> channel ids map */
{
size_t i;
for (i = 0; i < config->PassthroughCount; i++)
{
char* channel_name = config->Passthrough[i];
UINT64 channel_id = (UINT64)freerdp_channels_get_id_by_name(instance, channel_name);
HashTable_Add(pc->vc_ids, (void*)channel_name, (void*)channel_id);
}
}
/*
* after the connection fully established and settings were negotiated with target server, send
* a reactivation sequence to the client with the negotiated settings. This way, settings are
* synchorinized between proxy's peer and and remote target.
* after the connection fully established and settings were negotiated with target server,
* send a reactivation sequence to the client with the negotiated settings. This way,
* settings are synchorinized between proxy's peer and and remote target.
*/
return proxy_server_reactivate(ps, context);
}
@ -506,6 +589,8 @@ static void pf_client_client_free(freerdp* instance, rdpContext* context)
free(pc->frames_dir);
pc->frames_dir = NULL;
HashTable_Free(pc->vc_ids);
}
static int pf_client_client_stop(rdpContext* context)

View File

@ -154,6 +154,23 @@ static BOOL pf_config_load_channels(wIniFile* ini, proxyConfig* config)
config->Clipboard = pf_config_get_bool(ini, "Channels", "Clipboard");
config->AudioOutput = pf_config_get_bool(ini, "Channels", "AudioOutput");
config->RemoteApp = pf_config_get_bool(ini, "Channels", "RemoteApp");
config->Passthrough = CommandLineParseCommaSeparatedValues(
pf_config_get_str(ini, "Channels", "Passthrough"), &config->PassthroughCount);
{
/* validate channel name length */
size_t i;
for (i = 0; i < config->PassthroughCount; i++)
{
if (strlen(config->Passthrough[i]) > CHANNEL_NAME_LEN)
{
WLog_ERR(TAG, "passthrough channel: %s: name too long!", config->Passthrough[i]);
return FALSE;
}
}
}
return TRUE;
}
@ -284,6 +301,14 @@ out:
return NULL;
}
static void pf_server_config_print_list(char** list, size_t count)
{
size_t i;
for (i = 0; i < count; i++)
WLog_INFO(TAG, "\t\t- %s", list[i]);
}
void pf_server_config_print(proxyConfig* config)
{
WLog_INFO(TAG, "Proxy configuration:");
@ -321,6 +346,12 @@ void pf_server_config_print(proxyConfig* config)
CONFIG_PRINT_BOOL(config, AudioOutput);
CONFIG_PRINT_BOOL(config, RemoteApp);
if (config->PassthroughCount)
{
WLog_INFO(TAG, "\tStatic Channels Proxy:");
pf_server_config_print_list(config->Passthrough, config->PassthroughCount);
}
CONFIG_PRINT_SECTION("Clipboard");
CONFIG_PRINT_BOOL(config, TextOnly);
if (config->MaxTextLength > 0)
@ -336,6 +367,7 @@ void pf_server_config_free(proxyConfig* config)
if (config == NULL)
return;
free(config->Passthrough);
free(config->CapturesDirectory);
free(config->RequiredPlugins);
free(config->Modules);

View File

@ -58,6 +58,8 @@ struct proxy_config
BOOL Clipboard;
BOOL AudioOutput;
BOOL RemoteApp;
char** Passthrough;
size_t PassthroughCount;
/* clipboard specific settings */
BOOL TextOnly;

View File

@ -25,9 +25,25 @@
#include "pf_client.h"
#include "pf_context.h"
static wHashTable* create_channel_ids_map()
{
wHashTable* table = HashTable_New(TRUE);
if (!table)
return NULL;
table->hash = HashTable_StringHash;
table->keyCompare = HashTable_StringCompare;
table->keyClone = HashTable_StringClone;
table->keyFree = HashTable_StringFree;
return table;
}
/* Proxy context initialization callback */
static BOOL client_to_proxy_context_new(freerdp_peer* client, pServerContext* context)
{
proxyServer* server = (proxyServer*)client->ContextExtra;
proxyConfig* config = server->config;
context->dynvcReady = NULL;
context->vcm = WTSOpenServerA((LPSTR)client->context);
@ -38,6 +54,14 @@ static BOOL client_to_proxy_context_new(freerdp_peer* client, pServerContext* co
if (!(context->dynvcReady = CreateEvent(NULL, TRUE, FALSE, NULL)))
goto error;
context->vc_handles = (HANDLE*)calloc(config->PassthroughCount, sizeof(HANDLE));
if (!context->vc_handles)
goto error;
context->vc_ids = create_channel_ids_map();
if (!context->vc_ids)
goto error;
return TRUE;
error:
@ -50,6 +74,10 @@ error:
context->dynvcReady = NULL;
}
free(context->vc_handles);
context->vc_handles = NULL;
HashTable_Free(context->vc_ids);
context->vc_ids = NULL;
return FALSE;
}
@ -70,6 +98,9 @@ static void client_to_proxy_context_free(freerdp_peer* client, pServerContext* c
CloseHandle(context->dynvcReady);
context->dynvcReady = NULL;
}
HashTable_Free(context->vc_ids);
free(context->vc_handles);
}
BOOL pf_context_init_server_context(freerdp_peer* client)
@ -157,6 +188,10 @@ pClientContext* pf_context_create_client_context(rdpSettings* clientSettings)
if (!pf_context_copy_settings(context->settings, clientSettings))
goto error;
pc->vc_ids = create_channel_ids_map();
if (!pc->vc_ids)
goto error;
return pc;
error:
freerdp_client_context_free(context);

View File

@ -56,6 +56,9 @@ struct p_server_context
DispServerContext* disp;
CliprdrServerContext* cliprdr;
RdpsndServerContext* rdpsnd;
HANDLE* vc_handles; /* static virtual channels open handles */
wHashTable* vc_ids; /* channel_name -> channel_id map */
};
typedef struct p_server_context pServerContext;
@ -78,17 +81,19 @@ struct p_client_context
/*
* In a case when freerdp_connect fails,
* Used for NLA fallback feature, to check if the server should close the connection.
* When it is set to TRUE, proxy's client knows it shouldn't signal the server thread to closed
* the connection when pf_client_post_disconnect is called, because it is trying to connect
* reconnect without NLA. It must be set to TRUE before the first try, and to FALSE after the
* connection fully established, to ensure graceful shutdown of the connection when it will be
* closed.
* When it is set to TRUE, proxy's client knows it shouldn't signal the server thread to
* closed the connection when pf_client_post_disconnect is called, because it is trying to
* connect reconnect without NLA. It must be set to TRUE before the first try, and to FALSE
* after the connection fully established, to ensure graceful shutdown of the connection
* when it will be closed.
*/
BOOL allow_next_conn_failure;
/* session capture */
char* frames_dir;
UINT64 frames_count;
wHashTable* vc_ids; /* channel_name -> channel_id map */
};
typedef struct p_client_context pClientContext;

View File

@ -42,6 +42,8 @@ typedef BOOL (*moduleEntryPoint)(proxyPluginsManager* plugins_manager);
static const char* FILTER_TYPE_STRINGS[] = {
"KEYBOARD_EVENT",
"MOUSE_EVENT",
"CLIENT_CHANNEL_DATA",
"SERVER_CHANNEL_DATA",
};
static const char* HOOK_TYPE_STRINGS[] = {
@ -144,6 +146,13 @@ BOOL pf_modules_run_filter(PF_FILTER_TYPE type, proxyData* pdata, void* param)
IFCALLRET(plugin->MouseEvent, result, pdata, param);
break;
case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA:
IFCALLRET(plugin->ClientChannelData, result, pdata, param);
break;
case FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA:
IFCALLRET(plugin->ServerChannelData, result, pdata, param);
break;
default:
WLog_ERR(TAG, "invalid filter called");
}

View File

@ -31,6 +31,8 @@ enum _PF_FILTER_TYPE
{
FILTER_TYPE_KEYBOARD,
FILTER_TYPE_MOUSE,
FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA,
FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA,
FILTER_LAST
};

View File

@ -46,6 +46,8 @@
#define TAG PROXY_TAG("server")
static psPeerReceiveChannelData server_receive_channel_data_original = NULL;
static BOOL pf_server_parse_target_from_routing_token(rdpContext* context, char** target,
DWORD* port)
{
@ -196,6 +198,42 @@ static BOOL pf_server_adjust_monitor_layout(freerdp_peer* peer)
return TRUE;
}
static BOOL pf_server_receive_channel_data_hook(freerdp_peer* peer, UINT16 channelId,
const BYTE* data, int size, int flags,
int totalSize)
{
pServerContext* ps = (pServerContext*)peer->context;
pClientContext* pc = ps->pdata->pc;
proxyData* pdata = pc->pdata;
proxyConfig* config = pdata->config;
size_t i;
const char* channel_name = WTSChannelGetName(peer, channelId);
for (i = 0; i < config->PassthroughCount; i++)
{
if (strncmp(channel_name, config->Passthrough[i], CHANNEL_NAME_LEN) == 0)
{
proxyChannelDataEventInfo ev;
UINT64 client_channel_id;
ev.channel_id = channelId;
ev.channel_name = channel_name;
ev.data = data;
ev.data_len = size;
if (!pf_modules_run_filter(FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA, pdata, &ev))
return FALSE;
client_channel_id = (UINT64)HashTable_GetItemValue(pc->vc_ids, (void*)channel_name);
return pc->context.instance->SendChannelData(
pc->context.instance, (UINT16)client_channel_id, (BYTE*)data, size);
}
}
return server_receive_channel_data_original(peer, channelId, data, size, flags, totalSize);
}
static BOOL pf_server_initialize_peer_connection(freerdp_peer* peer)
{
pServerContext* ps = (pServerContext*)peer->context;
@ -257,6 +295,10 @@ static BOOL pf_server_initialize_peer_connection(freerdp_peer* peer)
peer->AdjustMonitorsLayout = pf_server_adjust_monitor_layout;
peer->settings->MultifragMaxRequestSize = 0xFFFFFF; /* FIXME */
/* virtual channels receive data hook */
server_receive_channel_data_original = peer->ReceiveChannelData;
peer->ReceiveChannelData = pf_server_receive_channel_data_hook;
if (ArrayList_Add(server->clients, pdata) < 0)
return FALSE;