From 60720e77065b3fc35287feccd2ecaff6b61ce0bc Mon Sep 17 00:00:00 2001 From: akallabeth Date: Thu, 6 Oct 2022 16:03:31 +0200 Subject: [PATCH] Improved streamdump file format --- client/Sample/tf_freerdp.c | 2 +- client/X11/cli/xfreerdp.c | 2 +- client/common/cmdline.c | 36 +++++++ client/common/cmdline.h | 2 + include/freerdp/streamdump.h | 14 ++- libfreerdp/core/streamdump.c | 133 +++++++++++++++++--------- libfreerdp/core/streamdump.h | 45 +++++++++ libfreerdp/core/test/TestStreamDump.c | 10 +- server/Sample/sfreerdp.c | 21 ++-- server/proxy/pf_server.c | 2 +- 10 files changed, 202 insertions(+), 65 deletions(-) create mode 100644 libfreerdp/core/streamdump.h diff --git a/client/Sample/tf_freerdp.c b/client/Sample/tf_freerdp.c index 4d7419d48..50c6713ef 100644 --- a/client/Sample/tf_freerdp.c +++ b/client/Sample/tf_freerdp.c @@ -395,7 +395,7 @@ int main(int argc, char* argv[]) 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; if (freerdp_client_start(context) != 0) diff --git a/client/X11/cli/xfreerdp.c b/client/X11/cli/xfreerdp.c index 83f60eb6d..92bbe7c55 100644 --- a/client/X11/cli/xfreerdp.c +++ b/client/X11/cli/xfreerdp.c @@ -65,7 +65,7 @@ int main(int argc, char* argv[]) 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; if (freerdp_client_start(context) != 0) diff --git a/client/common/cmdline.c b/client/common/cmdline.c index b9710df04..6611f67b4 100644 --- a/client/common/cmdline.c +++ b/client/common/cmdline.c @@ -2374,6 +2374,42 @@ int freerdp_client_settings_parse_command_line_arguments(rdpSettings* settings, { 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") { freerdp_settings_set_bool(settings, FreeRDP_DeactivateClientDecoding, enable); diff --git a/client/common/cmdline.h b/client/common/cmdline.h index b523437b2..3d21ddf86 100644 --- a/client/common/cmdline.h +++ b/client/common/cmdline.h @@ -125,6 +125,8 @@ static const COMMAND_LINE_ARGUMENT_A global_cmd_args[] = { "later\" option in MSTSC." }, { "drives", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, "Redirect all mount points as shares" }, + { "dump", COMMAND_LINE_VALUE_REQUIRED, ",", NULL, NULL, -1, NULL, + "record or replay dump" }, { "dvc", COMMAND_LINE_VALUE_REQUIRED, "[,]", NULL, NULL, -1, NULL, "Dynamic virtual channel" }, { "dynamic-resolution", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, diff --git a/include/freerdp/streamdump.h b/include/freerdp/streamdump.h index b073a3894..1ad9561c1 100644 --- a/include/freerdp/streamdump.h +++ b/include/freerdp/streamdump.h @@ -34,15 +34,19 @@ extern "C" { #endif - FREERDP_API BOOL stream_dump_read_line(FILE* fp, wStream* s, UINT64* pts, size_t* pOffset); - FREERDP_API BOOL stream_dump_write_line(FILE* fp, wStream* s); + typedef enum + { + 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); - 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); - 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 void stream_dump_free(rdpStreamDumpContext* dump); diff --git a/libfreerdp/core/streamdump.c b/libfreerdp/core/streamdump.c index ec9be6f7c..ff1dffc70 100644 --- a/libfreerdp/core/streamdump.c +++ b/libfreerdp/core/streamdump.c @@ -1,9 +1,10 @@ /** * FreeRDP: A Remote Desktop Protocol Implementation - * Static Virtual Channel Interface * - * Copyright 2021 Armin Novak - * Copyright 2021 Thincast Technologies GmbH + * 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. @@ -27,6 +28,8 @@ #include #include +#include "streamdump.h" + struct stream_dump_context { rdpTransportIo io; @@ -35,16 +38,41 @@ struct stream_dump_context size_t replayOffset; UINT64 replayTime; 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; UINT64 ts; UINT64 size = 0; size_t r; + UINT32 crc32; + BYTE received; - if (!fp || !s) + if (!fp || !s || !flags) return FALSE; 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); if (r != sizeof(ts)) 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); if (r != sizeof(size)) goto fail; + if (received) + *flags = STREAM_MSG_SRV_RX; + else + *flags = STREAM_MSG_SRV_TX; if (!Stream_EnsureRemainingCapacity(s, size)) goto fail; r = fread(Stream_Pointer(s), 1, size, fp); if (r != size) goto fail; + if (crc32 != crc32b(Stream_Pointer(s), size)) + goto fail; Stream_Seek(s, size); if (pOffset) @@ -80,7 +120,11 @@ fail: 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; const UINT64 t = GetTickCount64(); @@ -91,9 +135,17 @@ BOOL stream_dump_write_line(FILE* fp, wStream* s) 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); if (r != sizeof(t)) 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); if (r != sizeof(size)) goto fail; @@ -107,10 +159,9 @@ fail: 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; - char* folder = NULL; char* file = 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); if (!cfolder) - folder = GetKnownSubPath(KNOWN_PATH_TEMP, "freerdp-transport-dump"); + file = GetKnownSubPath(KNOWN_PATH_TEMP, "freerdp-transport-dump"); else - folder = _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); - } + file = _strdup(cfolder); if (!file) goto fail; fp = winpr_fopen(file, mode); fail: - free(folder); free(file); 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; FILE* fp; + const UINT32 mask = STREAM_MSG_SRV_RX | STREAM_MSG_SRV_TX; CONNECTION_STATE state = freerdp_get_state(context); int r; if (!context || !s || !offset) 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) return 0; - fp = stream_dump_get_file(context->settings, name, "ab"); + fp = stream_dump_get_file(context->settings, "ab"); if (!fp) return -1; @@ -178,7 +211,7 @@ SSIZE_T stream_dump_append(const rdpContext* context, const char* name, wStream* if (r < 0) goto fail; - if (!stream_dump_write_line(fp, s)) + if (!stream_dump_write_line(fp, flags, s)) goto fail; rc = _ftelli64(fp); if (rc < 0) @@ -189,7 +222,7 @@ fail: 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) { 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) return -1; - fp = stream_dump_get_file(context->settings, name, "rb"); + fp = stream_dump_get_file(context->settings, "rb"); if (!fp) return -1; r = _fseeki64(fp, *offset, SEEK_SET); if (r < 0) goto fail; - if (!stream_dump_read_line(fp, s, pts, offset)) + if (!stream_dump_read_line(fp, s, pts, offset, flags)) goto fail; rc = _ftelli64(fp); @@ -223,7 +256,8 @@ static int stream_dump_transport_write(rdpTransport* transport, wStream* s) WINPR_ASSERT(ctx->dump); 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) return -1; @@ -244,7 +278,9 @@ static int stream_dump_transport_read(rdpTransport* transport, wStream* s) rc = ctx->dump->io.ReadPdu(transport, s); 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) return -1; } @@ -295,13 +331,17 @@ static int stream_dump_replay_transport_read(rdpTransport* transport, wStream* s size_t size = 0; time_t slp = 0; UINT64 ts = 0; + UINT32 flags = 0; WINPR_ASSERT(ctx); WINPR_ASSERT(ctx->dump); WINPR_ASSERT(s); - if (stream_dump_get(ctx, NULL, s, &ctx->dump->replayOffset, &ts) < 0) - return -1; + do + { + if (stream_dump_get(ctx, &flags, s, &ctx->dump->replayOffset, &ts) < 0) + return -1; + } while (flags & STREAM_MSG_SRV_RX); if ((ctx->dump->replayTime > 0) && (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); } -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->dump); context->dump->state = state; + context->dump->isServer = isServer; if (!stream_dump_register_write_handlers(context)) return FALSE; return stream_dump_register_read_handlers(context); diff --git a/libfreerdp/core/streamdump.h b/libfreerdp/core/streamdump.h new file mode 100644 index 000000000..759a76c32 --- /dev/null +++ b/libfreerdp/core/streamdump.h @@ -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 +#include +#include + +#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 diff --git a/libfreerdp/core/test/TestStreamDump.c b/libfreerdp/core/test/TestStreamDump.c index 3ff2aff0e..71528daa1 100644 --- a/libfreerdp/core/test/TestStreamDump.c +++ b/libfreerdp/core/test/TestStreamDump.c @@ -7,6 +7,8 @@ #include #include +#include "../streamdump.h" + static BOOL test_entry_read_write(void) { BOOL rc = FALSE; @@ -14,10 +16,12 @@ static BOOL test_entry_read_write(void) wStream *sw = NULL, *sr = NULL; size_t offset = 0, x; UINT64 ts = 0; + UINT32 flags = 0; BYTE tmp[16] = { 0 }; char tmp2[64] = { 0 }; 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)); @@ -45,14 +49,14 @@ static BOOL test_entry_read_write(void) fp = fopen(name, "wb"); if (!fp) goto fail; - if (!stream_dump_write_line(fp, sw)) + if (!stream_dump_write_line(fp, 0, sw)) goto fail; fclose(fp); fp = fopen(name, "rb"); if (!fp) goto fail; - if (!stream_dump_read_line(fp, sr, &ts, &offset)) + if (!stream_dump_read_line(fp, sr, &ts, &offset, &flags)) goto fail; if (entrysize != offset) diff --git a/server/Sample/sfreerdp.c b/server/Sample/sfreerdp.c index 2c9f88199..15bfeecf8 100644 --- a/server/Sample/sfreerdp.c +++ b/server/Sample/sfreerdp.c @@ -923,6 +923,7 @@ static int hook_peer_write_pdu(rdpTransport* transport, wStream* s) CONNECTION_STATE state; testPeerContext* peerCtx; size_t offset = 0; + UINT32 flags = 0; rdpContext* context = transport_get_context(transport); WINPR_ASSERT(context); @@ -954,18 +955,22 @@ static int hook_peer_write_pdu(rdpTransport* transport, wStream* s) if (!ls) goto fail; - while (stream_dump_get(context, NULL, ls, &offset, &ts) > 0) + while (stream_dump_get(context, &flags, ls, &offset, &ts) > 0) { int rc; - if ((last_ts > 0) && (ts > last_ts)) + /* Skip messages from client. */ + if (flags & STREAM_MSG_SRV_TX) { - UINT64 diff = ts - last_ts; - Sleep(diff); + if ((last_ts > 0) && (ts > last_ts)) + { + UINT64 diff = ts - last_ts; + Sleep(diff); + } + last_ts = ts; + rc = peerCtx->io.WritePdu(transport, ls); + if (rc < 0) + goto fail; } - last_ts = ts; - rc = peerCtx->io.WritePdu(transport, ls); - if (rc < 0) - goto fail; Stream_SetPosition(ls, 0); } diff --git a/server/proxy/pf_server.c b/server/proxy/pf_server.c index d60ff6b2a..80c821c15 100644 --- a/server/proxy/pf_server.c +++ b/server/proxy/pf_server.c @@ -542,7 +542,7 @@ static BOOL pf_server_initialize_peer_connection(freerdp_peer* peer) pdata->server_receive_channel_data_original = peer->ReceiveChannelData; 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 TRUE; }