server: proxy: support static vc passthrough
This commit is contained in:
parent
9417350d92
commit
079871ac65
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -58,6 +58,8 @@ struct proxy_config
|
||||
BOOL Clipboard;
|
||||
BOOL AudioOutput;
|
||||
BOOL RemoteApp;
|
||||
char** Passthrough;
|
||||
size_t PassthroughCount;
|
||||
|
||||
/* clipboard specific settings */
|
||||
BOOL TextOnly;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user