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;
}
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)

View File

@ -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)

View File

@ -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);

View File

@ -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, "<record|replay>,<file>", NULL, NULL, -1, NULL,
"record or replay dump" },
{ "dvc", COMMAND_LINE_VALUE_REQUIRED, "<channel>[,<options>]", NULL, NULL, -1, NULL,
"Dynamic virtual channel" },
{ "dynamic-resolution", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,

View File

@ -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);

View File

@ -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 <freerdp/streamdump.h>
#include <freerdp/transport_io.h>
#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);

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/streamdump.h>
#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)

View File

@ -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);
}

View File

@ -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;
}