Improved streamdump file format

This commit is contained in:
akallabeth 2022-10-06 16:03:31 +02:00 committed by Martin Fleisz
parent 2a6950f366
commit 60720e7706
10 changed files with 202 additions and 65 deletions

View File

@ -395,7 +395,7 @@ int main(int argc, char* argv[])
goto fail; goto fail;
} }
if (!stream_dump_register_handlers(context, CONNECTION_STATE_MCS_CONNECT)) if (!stream_dump_register_handlers(context, CONNECTION_STATE_MCS_CONNECT, FALSE))
goto fail; goto fail;
if (freerdp_client_start(context) != 0) if (freerdp_client_start(context) != 0)

View File

@ -65,7 +65,7 @@ int main(int argc, char* argv[])
goto out; goto out;
} }
if (!stream_dump_register_handlers(context, CONNECTION_STATE_MCS_CONNECT)) if (!stream_dump_register_handlers(context, CONNECTION_STATE_MCS_CONNECT, FALSE))
goto out; goto out;
if (freerdp_client_start(context) != 0) if (freerdp_client_start(context) != 0)

View File

@ -2374,6 +2374,42 @@ int freerdp_client_settings_parse_command_line_arguments(rdpSettings* settings,
{ {
settings->RedirectDrives = enable; settings->RedirectDrives = enable;
} }
CommandLineSwitchCase(arg, "dump")
{
BOOL failed = FALSE;
size_t count = 0;
char** args = CommandLineParseCommaSeparatedValues(arg->Value, &count);
if (!args || (count != 2))
failed = TRUE;
else
{
if (!freerdp_settings_set_string(settings, FreeRDP_TransportDumpFile, args[1]))
failed = TRUE;
else if (strcmp(args[0], "replay") == 0)
{
if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDump, FALSE))
failed = TRUE;
else if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDumpReplay,
TRUE))
failed = TRUE;
}
else if (strcmp(args[0], "record") == 0)
{
if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDump, TRUE))
failed = TRUE;
else if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDumpReplay,
FALSE))
failed = TRUE;
}
else
{
failed = TRUE;
}
}
free(args);
if (failed)
return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
}
CommandLineSwitchCase(arg, "disable-output") CommandLineSwitchCase(arg, "disable-output")
{ {
freerdp_settings_set_bool(settings, FreeRDP_DeactivateClientDecoding, enable); freerdp_settings_set_bool(settings, FreeRDP_DeactivateClientDecoding, enable);

View File

@ -125,6 +125,8 @@ static const COMMAND_LINE_ARGUMENT_A global_cmd_args[] = {
"later\" option in MSTSC." }, "later\" option in MSTSC." },
{ "drives", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, { "drives", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
"Redirect all mount points as shares" }, "Redirect all mount points as shares" },
{ "dump", COMMAND_LINE_VALUE_REQUIRED, "<record|replay>,<file>", NULL, NULL, -1, NULL,
"record or replay dump" },
{ "dvc", COMMAND_LINE_VALUE_REQUIRED, "<channel>[,<options>]", NULL, NULL, -1, NULL, { "dvc", COMMAND_LINE_VALUE_REQUIRED, "<channel>[,<options>]", NULL, NULL, -1, NULL,
"Dynamic virtual channel" }, "Dynamic virtual channel" },
{ "dynamic-resolution", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, { "dynamic-resolution", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,

View File

@ -34,15 +34,19 @@ extern "C"
{ {
#endif #endif
FREERDP_API BOOL stream_dump_read_line(FILE* fp, wStream* s, UINT64* pts, size_t* pOffset); typedef enum
FREERDP_API BOOL stream_dump_write_line(FILE* fp, wStream* s); {
STREAM_MSG_SRV_RX = 1,
STREAM_MSG_SRV_TX = 2
} StreamDumpDirection;
FREERDP_API SSIZE_T stream_dump_append(const rdpContext* context, const char* name, wStream* s, FREERDP_API SSIZE_T stream_dump_append(const rdpContext* context, UINT32 flags, wStream* s,
size_t* offset); size_t* offset);
FREERDP_API SSIZE_T stream_dump_get(const rdpContext* context, const char* name, wStream* s, FREERDP_API SSIZE_T stream_dump_get(const rdpContext* context, UINT32* flags, wStream* s,
size_t* offset, UINT64* pts); size_t* offset, UINT64* pts);
FREERDP_API BOOL stream_dump_register_handlers(rdpContext* context, CONNECTION_STATE state); FREERDP_API BOOL stream_dump_register_handlers(rdpContext* context, CONNECTION_STATE state,
BOOL isServer);
FREERDP_API rdpStreamDumpContext* stream_dump_new(void); FREERDP_API rdpStreamDumpContext* stream_dump_new(void);
FREERDP_API void stream_dump_free(rdpStreamDumpContext* dump); FREERDP_API void stream_dump_free(rdpStreamDumpContext* dump);

View File

@ -1,9 +1,10 @@
/** /**
* FreeRDP: A Remote Desktop Protocol Implementation * FreeRDP: A Remote Desktop Protocol Implementation
* Static Virtual Channel Interface
* *
* Copyright 2021 Armin Novak * RDP session stream dump interface
* Copyright 2021 Thincast Technologies GmbH *
* Copyright 2022 Armin Novak
* Copyright 2022 Thincast Technologies GmbH
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -27,6 +28,8 @@
#include <freerdp/streamdump.h> #include <freerdp/streamdump.h>
#include <freerdp/transport_io.h> #include <freerdp/transport_io.h>
#include "streamdump.h"
struct stream_dump_context struct stream_dump_context
{ {
rdpTransportIo io; rdpTransportIo io;
@ -35,16 +38,41 @@ struct stream_dump_context
size_t replayOffset; size_t replayOffset;
UINT64 replayTime; UINT64 replayTime;
CONNECTION_STATE state; CONNECTION_STATE state;
BOOL isServer;
}; };
BOOL stream_dump_read_line(FILE* fp, wStream* s, UINT64* pts, size_t* pOffset) static UINT32 crc32b(const BYTE* data, size_t length)
{
size_t x;
UINT32 crc = 0xFFFFFFFF;
for (x = 0; x < length; x++)
{
const UINT32 d = data[x] & 0xFF;
crc = crc ^ d;
for (int j = 7; j >= 0; j--)
{
UINT32 mask = -(crc & 1);
crc = (crc >> 1) ^ (0xEDB88320 & mask);
}
}
return ~crc;
}
#if !defined(BUILD_TESTING)
static
#endif
BOOL
stream_dump_read_line(FILE* fp, wStream* s, UINT64* pts, size_t* pOffset, UINT32* flags)
{ {
BOOL rc = FALSE; BOOL rc = FALSE;
UINT64 ts; UINT64 ts;
UINT64 size = 0; UINT64 size = 0;
size_t r; size_t r;
UINT32 crc32;
BYTE received;
if (!fp || !s) if (!fp || !s || !flags)
return FALSE; return FALSE;
if (pOffset) if (pOffset)
@ -53,14 +81,26 @@ BOOL stream_dump_read_line(FILE* fp, wStream* s, UINT64* pts, size_t* pOffset)
r = fread(&ts, 1, sizeof(ts), fp); r = fread(&ts, 1, sizeof(ts), fp);
if (r != sizeof(ts)) if (r != sizeof(ts))
goto fail; goto fail;
r = fread(&received, 1, sizeof(received), fp);
if (r != sizeof(received))
goto fail;
r = fread(&crc32, 1, sizeof(crc32), fp);
if (r != sizeof(crc32))
goto fail;
r = fread(&size, 1, sizeof(size), fp); r = fread(&size, 1, sizeof(size), fp);
if (r != sizeof(size)) if (r != sizeof(size))
goto fail; goto fail;
if (received)
*flags = STREAM_MSG_SRV_RX;
else
*flags = STREAM_MSG_SRV_TX;
if (!Stream_EnsureRemainingCapacity(s, size)) if (!Stream_EnsureRemainingCapacity(s, size))
goto fail; goto fail;
r = fread(Stream_Pointer(s), 1, size, fp); r = fread(Stream_Pointer(s), 1, size, fp);
if (r != size) if (r != size)
goto fail; goto fail;
if (crc32 != crc32b(Stream_Pointer(s), size))
goto fail;
Stream_Seek(s, size); Stream_Seek(s, size);
if (pOffset) if (pOffset)
@ -80,7 +120,11 @@ fail:
return rc; return rc;
} }
BOOL stream_dump_write_line(FILE* fp, wStream* s) #if !defined(BUILD_TESTING)
static
#endif
BOOL
stream_dump_write_line(FILE* fp, UINT32 flags, wStream* s)
{ {
BOOL rc = FALSE; BOOL rc = FALSE;
const UINT64 t = GetTickCount64(); const UINT64 t = GetTickCount64();
@ -91,9 +135,17 @@ BOOL stream_dump_write_line(FILE* fp, wStream* s)
return FALSE; return FALSE;
{ {
const UINT32 crc32 = crc32b(data, size);
const BYTE received = flags & STREAM_MSG_SRV_RX;
size_t r = fwrite(&t, 1, sizeof(t), fp); size_t r = fwrite(&t, 1, sizeof(t), fp);
if (r != sizeof(t)) if (r != sizeof(t))
goto fail; goto fail;
r = fwrite(&received, 1, sizeof(received), fp);
if (r != sizeof(received))
goto fail;
r = fwrite(&crc32, 1, sizeof(crc32), fp);
if (r != sizeof(crc32))
goto fail;
r = fwrite(&size, 1, sizeof(size), fp); r = fwrite(&size, 1, sizeof(size), fp);
if (r != sizeof(size)) if (r != sizeof(size))
goto fail; goto fail;
@ -107,10 +159,9 @@ fail:
return rc; return rc;
} }
static FILE* stream_dump_get_file(const rdpSettings* settings, const char* name, const char* mode) static FILE* stream_dump_get_file(const rdpSettings* settings, const char* mode)
{ {
const char* cfolder; const char* cfolder;
char* folder = NULL;
char* file = NULL; char* file = NULL;
FILE* fp = NULL; FILE* fp = NULL;
@ -119,58 +170,40 @@ static FILE* stream_dump_get_file(const rdpSettings* settings, const char* name,
cfolder = freerdp_settings_get_string(settings, FreeRDP_TransportDumpFile); cfolder = freerdp_settings_get_string(settings, FreeRDP_TransportDumpFile);
if (!cfolder) if (!cfolder)
folder = GetKnownSubPath(KNOWN_PATH_TEMP, "freerdp-transport-dump"); file = GetKnownSubPath(KNOWN_PATH_TEMP, "freerdp-transport-dump");
else else
folder = _strdup(cfolder); file = _strdup(cfolder);
if (!folder)
goto fail;
if (name)
{
char buffer[8192] = { 0 };
int rc = _snprintf(buffer, sizeof(buffer), "%s.dump", name);
if ((rc <= 0) || ((size_t)rc >= sizeof(buffer)))
goto fail;
if (!winpr_PathFileExists(folder))
{
if (!winpr_PathMakePath(folder, NULL))
goto fail;
}
file = GetCombinedPath(folder, buffer);
}
else
{
if (!winpr_PathFileExists(folder))
goto fail;
file = _strdup(folder);
}
if (!file) if (!file)
goto fail; goto fail;
fp = winpr_fopen(file, mode); fp = winpr_fopen(file, mode);
fail: fail:
free(folder);
free(file); free(file);
return fp; return fp;
} }
SSIZE_T stream_dump_append(const rdpContext* context, const char* name, wStream* s, size_t* offset) SSIZE_T stream_dump_append(const rdpContext* context, UINT32 flags, wStream* s, size_t* offset)
{ {
SSIZE_T rc = -1; SSIZE_T rc = -1;
FILE* fp; FILE* fp;
const UINT32 mask = STREAM_MSG_SRV_RX | STREAM_MSG_SRV_TX;
CONNECTION_STATE state = freerdp_get_state(context); CONNECTION_STATE state = freerdp_get_state(context);
int r; int r;
if (!context || !s || !offset) if (!context || !s || !offset)
return -1; return -1;
if ((flags & STREAM_MSG_SRV_RX) && (flags & STREAM_MSG_SRV_TX))
return -1;
if ((flags & mask) == 0)
return -1;
if (state < context->dump->state) if (state < context->dump->state)
return 0; return 0;
fp = stream_dump_get_file(context->settings, name, "ab"); fp = stream_dump_get_file(context->settings, "ab");
if (!fp) if (!fp)
return -1; return -1;
@ -178,7 +211,7 @@ SSIZE_T stream_dump_append(const rdpContext* context, const char* name, wStream*
if (r < 0) if (r < 0)
goto fail; goto fail;
if (!stream_dump_write_line(fp, s)) if (!stream_dump_write_line(fp, flags, s))
goto fail; goto fail;
rc = _ftelli64(fp); rc = _ftelli64(fp);
if (rc < 0) if (rc < 0)
@ -189,7 +222,7 @@ fail:
return rc; return rc;
} }
SSIZE_T stream_dump_get(const rdpContext* context, const char* name, wStream* s, size_t* offset, SSIZE_T stream_dump_get(const rdpContext* context, UINT32* flags, wStream* s, size_t* offset,
UINT64* pts) UINT64* pts)
{ {
SSIZE_T rc = -1; SSIZE_T rc = -1;
@ -198,14 +231,14 @@ SSIZE_T stream_dump_get(const rdpContext* context, const char* name, wStream* s,
if (!context || !s || !offset) if (!context || !s || !offset)
return -1; return -1;
fp = stream_dump_get_file(context->settings, name, "rb"); fp = stream_dump_get_file(context->settings, "rb");
if (!fp) if (!fp)
return -1; return -1;
r = _fseeki64(fp, *offset, SEEK_SET); r = _fseeki64(fp, *offset, SEEK_SET);
if (r < 0) if (r < 0)
goto fail; goto fail;
if (!stream_dump_read_line(fp, s, pts, offset)) if (!stream_dump_read_line(fp, s, pts, offset, flags))
goto fail; goto fail;
rc = _ftelli64(fp); rc = _ftelli64(fp);
@ -223,7 +256,8 @@ static int stream_dump_transport_write(rdpTransport* transport, wStream* s)
WINPR_ASSERT(ctx->dump); WINPR_ASSERT(ctx->dump);
WINPR_ASSERT(s); WINPR_ASSERT(s);
r = stream_dump_append(ctx, "write", s, &ctx->dump->writeDumpOffset); r = stream_dump_append(ctx, ctx->dump->isServer ? STREAM_MSG_SRV_TX : STREAM_MSG_SRV_RX, s,
&ctx->dump->writeDumpOffset);
if (r < 0) if (r < 0)
return -1; return -1;
@ -244,7 +278,9 @@ static int stream_dump_transport_read(rdpTransport* transport, wStream* s)
rc = ctx->dump->io.ReadPdu(transport, s); rc = ctx->dump->io.ReadPdu(transport, s);
if (rc > 0) if (rc > 0)
{ {
SSIZE_T r = stream_dump_append(ctx, "read", s, &ctx->dump->readDumpOffset); SSIZE_T r =
stream_dump_append(ctx, ctx->dump->isServer ? STREAM_MSG_SRV_RX : STREAM_MSG_SRV_TX, s,
&ctx->dump->readDumpOffset);
if (r < 0) if (r < 0)
return -1; return -1;
} }
@ -295,13 +331,17 @@ static int stream_dump_replay_transport_read(rdpTransport* transport, wStream* s
size_t size = 0; size_t size = 0;
time_t slp = 0; time_t slp = 0;
UINT64 ts = 0; UINT64 ts = 0;
UINT32 flags = 0;
WINPR_ASSERT(ctx); WINPR_ASSERT(ctx);
WINPR_ASSERT(ctx->dump); WINPR_ASSERT(ctx->dump);
WINPR_ASSERT(s); WINPR_ASSERT(s);
if (stream_dump_get(ctx, NULL, s, &ctx->dump->replayOffset, &ts) < 0) do
{
if (stream_dump_get(ctx, &flags, s, &ctx->dump->replayOffset, &ts) < 0)
return -1; return -1;
} while (flags & STREAM_MSG_SRV_RX);
if ((ctx->dump->replayTime > 0) && (ts > ctx->dump->replayTime)) if ((ctx->dump->replayTime > 0) && (ts > ctx->dump->replayTime))
slp = ts - ctx->dump->replayTime; slp = ts - ctx->dump->replayTime;
@ -368,11 +408,12 @@ static BOOL stream_dump_register_read_handlers(rdpContext* context)
return freerdp_set_io_callbacks(context, &dump); return freerdp_set_io_callbacks(context, &dump);
} }
BOOL stream_dump_register_handlers(rdpContext* context, CONNECTION_STATE state) BOOL stream_dump_register_handlers(rdpContext* context, CONNECTION_STATE state, BOOL isServer)
{ {
WINPR_ASSERT(context); WINPR_ASSERT(context);
WINPR_ASSERT(context->dump); WINPR_ASSERT(context->dump);
context->dump->state = state; context->dump->state = state;
context->dump->isServer = isServer;
if (!stream_dump_register_write_handlers(context)) if (!stream_dump_register_write_handlers(context))
return FALSE; return FALSE;
return stream_dump_register_read_handlers(context); return stream_dump_register_read_handlers(context);

View File

@ -0,0 +1,45 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
*
* RDP session stream dump interface
*
* Copyright 2022 Armin Novak
* Copyright 2022 Thincast Technologies GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FREERDP_STREAMDUMP_INTERNAL
#define FREERDP_STREAMDUMP_INTERNAL
#include <freerdp/api.h>
#include <winpr/wtypes.h>
#include <winpr/stream.h>
#if !defined(BUILD_TESTING)
static
#else
FREERDP_LOCAL
#endif
BOOL
stream_dump_read_line(FILE* fp, wStream* s, UINT64* pts, size_t* pOffset, UINT32* flags);
#if !defined(BUILD_TESTING)
static
#else
FREERDP_LOCAL
#endif
BOOL
stream_dump_write_line(FILE* fp, UINT32 flags, wStream* s);
#endif

View File

@ -7,6 +7,8 @@
#include <freerdp/freerdp.h> #include <freerdp/freerdp.h>
#include <freerdp/streamdump.h> #include <freerdp/streamdump.h>
#include "../streamdump.h"
static BOOL test_entry_read_write(void) static BOOL test_entry_read_write(void)
{ {
BOOL rc = FALSE; BOOL rc = FALSE;
@ -14,10 +16,12 @@ static BOOL test_entry_read_write(void)
wStream *sw = NULL, *sr = NULL; wStream *sw = NULL, *sr = NULL;
size_t offset = 0, x; size_t offset = 0, x;
UINT64 ts = 0; UINT64 ts = 0;
UINT32 flags = 0;
BYTE tmp[16] = { 0 }; BYTE tmp[16] = { 0 };
char tmp2[64] = { 0 }; char tmp2[64] = { 0 };
char* name = NULL; char* name = NULL;
size_t entrysize = sizeof(UINT64) + sizeof(UINT64); size_t entrysize = sizeof(UINT64) /* timestamp */ + sizeof(BYTE) /* direction */ +
sizeof(UINT32) /* CRC */ + sizeof(UINT64) /* size */;
winpr_RAND(tmp, sizeof(tmp)); winpr_RAND(tmp, sizeof(tmp));
@ -45,14 +49,14 @@ static BOOL test_entry_read_write(void)
fp = fopen(name, "wb"); fp = fopen(name, "wb");
if (!fp) if (!fp)
goto fail; goto fail;
if (!stream_dump_write_line(fp, sw)) if (!stream_dump_write_line(fp, 0, sw))
goto fail; goto fail;
fclose(fp); fclose(fp);
fp = fopen(name, "rb"); fp = fopen(name, "rb");
if (!fp) if (!fp)
goto fail; goto fail;
if (!stream_dump_read_line(fp, sr, &ts, &offset)) if (!stream_dump_read_line(fp, sr, &ts, &offset, &flags))
goto fail; goto fail;
if (entrysize != offset) if (entrysize != offset)

View File

@ -923,6 +923,7 @@ static int hook_peer_write_pdu(rdpTransport* transport, wStream* s)
CONNECTION_STATE state; CONNECTION_STATE state;
testPeerContext* peerCtx; testPeerContext* peerCtx;
size_t offset = 0; size_t offset = 0;
UINT32 flags = 0;
rdpContext* context = transport_get_context(transport); rdpContext* context = transport_get_context(transport);
WINPR_ASSERT(context); WINPR_ASSERT(context);
@ -954,9 +955,12 @@ static int hook_peer_write_pdu(rdpTransport* transport, wStream* s)
if (!ls) if (!ls)
goto fail; goto fail;
while (stream_dump_get(context, NULL, ls, &offset, &ts) > 0) while (stream_dump_get(context, &flags, ls, &offset, &ts) > 0)
{ {
int rc; int rc;
/* Skip messages from client. */
if (flags & STREAM_MSG_SRV_TX)
{
if ((last_ts > 0) && (ts > last_ts)) if ((last_ts > 0) && (ts > last_ts))
{ {
UINT64 diff = ts - last_ts; UINT64 diff = ts - last_ts;
@ -966,6 +970,7 @@ static int hook_peer_write_pdu(rdpTransport* transport, wStream* s)
rc = peerCtx->io.WritePdu(transport, ls); rc = peerCtx->io.WritePdu(transport, ls);
if (rc < 0) if (rc < 0)
goto fail; goto fail;
}
Stream_SetPosition(ls, 0); Stream_SetPosition(ls, 0);
} }

View File

@ -542,7 +542,7 @@ static BOOL pf_server_initialize_peer_connection(freerdp_peer* peer)
pdata->server_receive_channel_data_original = peer->ReceiveChannelData; pdata->server_receive_channel_data_original = peer->ReceiveChannelData;
peer->ReceiveChannelData = pf_server_receive_channel_data_hook; peer->ReceiveChannelData = pf_server_receive_channel_data_hook;
if (!stream_dump_register_handlers(peer->context, CONNECTION_STATE_NEGO)) if (!stream_dump_register_handlers(peer->context, CONNECTION_STATE_NEGO, TRUE))
return FALSE; return FALSE;
return TRUE; return TRUE;
} }