2021-08-18 07:36:31 -04:00
|
|
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
|
|
|
|
#include "Pch.h"
|
|
|
|
|
#include "AsioIoable.h"
|
|
|
|
|
#include "AsioSocket.h"
|
|
|
|
|
#include "Recorder.h"
|
|
|
|
|
#include "Store.h"
|
|
|
|
|
|
|
|
|
|
#if TS_USING(TS_PLATFORM_WINDOWS)
|
|
|
|
|
# include <Mstcpip.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
class FRecorderRelay
|
|
|
|
|
: public FAsioIoSink
|
|
|
|
|
{
|
|
|
|
|
public:
|
2022-11-22 07:39:38 -05:00
|
|
|
FRecorderRelay(asio::ip::tcp::socket& Socket, FStore& InStore);
|
2021-08-18 07:36:31 -04:00
|
|
|
virtual ~FRecorderRelay();
|
|
|
|
|
bool IsOpen();
|
|
|
|
|
void Close();
|
2022-11-22 07:39:38 -05:00
|
|
|
uint32 GetTraceId() const;
|
2021-08-18 07:36:31 -04:00
|
|
|
uint32 GetIpAddress() const;
|
|
|
|
|
uint32 GetControlPort() const;
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
virtual void OnIoComplete(uint32 Id, int32 Size) override;
|
2022-11-22 07:39:38 -05:00
|
|
|
bool CreateTrace();
|
2022-11-23 03:48:46 -05:00
|
|
|
bool ReadMagic();
|
2021-08-18 07:36:31 -04:00
|
|
|
bool ReadMetadata(int32 Size);
|
|
|
|
|
static const uint32 BufferSize = 64 * 1024;
|
|
|
|
|
FAsioSocket Input;
|
2022-11-22 07:39:38 -05:00
|
|
|
FAsioWriteable* Output = nullptr;
|
|
|
|
|
FStore& Store;
|
2022-11-23 03:48:46 -05:00
|
|
|
uint8* PreambleCursor;
|
2022-11-22 07:39:38 -05:00
|
|
|
uint32 TraceId = 0;
|
2021-08-18 07:36:31 -04:00
|
|
|
uint16 ControlPort = 0;
|
|
|
|
|
uint8 Buffer[BufferSize];
|
2022-11-22 07:39:15 -05:00
|
|
|
|
|
|
|
|
enum
|
|
|
|
|
{
|
2022-11-23 03:48:46 -05:00
|
|
|
OpMagicRead,
|
|
|
|
|
OpMetadataRead,
|
2022-11-22 07:39:15 -05:00
|
|
|
OpSocketRead,
|
|
|
|
|
OpFileWrite,
|
|
|
|
|
};
|
2022-11-23 03:48:46 -05:00
|
|
|
|
|
|
|
|
using MagicType = uint32;
|
|
|
|
|
using MetadataSizeType = uint16;
|
|
|
|
|
using VersionType = struct { uint8 Transport; uint8 Protocol; };
|
|
|
|
|
|
|
|
|
|
static_assert(sizeof(VersionType) == 2, "Unexpected struct size");
|
2021-08-18 07:36:31 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2022-11-22 07:39:38 -05:00
|
|
|
FRecorderRelay::FRecorderRelay(asio::ip::tcp::socket& Socket, FStore& InStore)
|
2021-08-18 07:36:31 -04:00
|
|
|
: Input(Socket)
|
2022-11-22 07:39:38 -05:00
|
|
|
, Store(InStore)
|
2021-08-18 07:36:31 -04:00
|
|
|
{
|
2022-11-22 07:38:47 -05:00
|
|
|
#if TS_USING(TS_PLATFORM_WINDOWS)
|
|
|
|
|
// Trace data is a stream and communication is one way. It is implemented
|
|
|
|
|
// this way to share code between sending trace data over the wire and writing
|
|
|
|
|
// it to a file. Because there's no ping/pong we can end up with a half-open
|
|
|
|
|
// TCP connection if the other end doesn't close its socket. So we'll enable
|
|
|
|
|
// keep-alive on the socket and set a short timeout (default is 2hrs).
|
|
|
|
|
tcp_keepalive KeepAlive =
|
|
|
|
|
{
|
|
|
|
|
1, // on
|
|
|
|
|
15000, // timeout_ms
|
|
|
|
|
2000, // interval_ms
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
DWORD BytesReturned;
|
|
|
|
|
WSAIoctl(
|
|
|
|
|
Socket.native_handle(),
|
|
|
|
|
SIO_KEEPALIVE_VALS,
|
|
|
|
|
&KeepAlive, sizeof(KeepAlive),
|
|
|
|
|
nullptr, 0,
|
|
|
|
|
&BytesReturned,
|
|
|
|
|
nullptr,
|
|
|
|
|
nullptr
|
|
|
|
|
);
|
|
|
|
|
#endif
|
|
|
|
|
|
2022-11-23 03:48:46 -05:00
|
|
|
// Kick things off by reading the magic four bytes at the start of the stream
|
|
|
|
|
// along with an additional two bytes that are likely the metadata size.
|
|
|
|
|
uint32 PreambleReadSize = sizeof(MagicType) + sizeof(MetadataSizeType);
|
|
|
|
|
PreambleCursor = Buffer + PreambleReadSize;
|
|
|
|
|
Input.Read(Buffer, PreambleReadSize, this, OpMagicRead);
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
FRecorderRelay::~FRecorderRelay()
|
|
|
|
|
{
|
|
|
|
|
check(!Input.IsOpen());
|
2022-11-22 07:39:38 -05:00
|
|
|
if (Output != nullptr)
|
|
|
|
|
{
|
|
|
|
|
check(!Output->IsOpen());
|
|
|
|
|
delete Output;
|
|
|
|
|
}
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
bool FRecorderRelay::IsOpen()
|
|
|
|
|
{
|
|
|
|
|
return Input.IsOpen();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
void FRecorderRelay::Close()
|
|
|
|
|
{
|
|
|
|
|
Input.Close();
|
2022-11-22 07:39:38 -05:00
|
|
|
if (Output != nullptr)
|
|
|
|
|
{
|
|
|
|
|
Output->Close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
uint32 FRecorderRelay::GetTraceId() const
|
|
|
|
|
{
|
|
|
|
|
return TraceId;
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
uint32 FRecorderRelay::GetIpAddress() const
|
|
|
|
|
{
|
|
|
|
|
return Input.GetRemoteAddress();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
uint32 FRecorderRelay::GetControlPort() const
|
|
|
|
|
{
|
|
|
|
|
return ControlPort;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-22 07:39:38 -05:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
bool FRecorderRelay::CreateTrace()
|
|
|
|
|
{
|
|
|
|
|
FStore::FNewTrace Trace = Store.CreateTrace();
|
|
|
|
|
TraceId = Trace.Id;
|
|
|
|
|
Output = Trace.Writeable;
|
|
|
|
|
return (Output != nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-22 08:02:11 -05:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2022-11-23 03:48:46 -05:00
|
|
|
bool FRecorderRelay::ReadMagic()
|
2022-11-22 08:02:11 -05:00
|
|
|
{
|
2022-11-23 03:48:46 -05:00
|
|
|
// Here we'll check the magic four bytes at the start of the stream and create
|
|
|
|
|
// a trace to write into if they are bytes we're expecting.
|
|
|
|
|
|
|
|
|
|
// We will only support clients that send the magic. Very early clients did
|
|
|
|
|
// not do this but they were unreleased and should no longer be in use.
|
|
|
|
|
if (Buffer[3] != 'T' || Buffer[2] != 'R' || Buffer[1] != 'C')
|
2022-11-22 08:02:11 -05:00
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-23 03:48:46 -05:00
|
|
|
// We can continue to support very old clients
|
|
|
|
|
if (Buffer[0] == 'E')
|
|
|
|
|
{
|
|
|
|
|
if (CreateTrace())
|
|
|
|
|
{
|
|
|
|
|
// Old clients have no metadata so we can go straight into the
|
|
|
|
|
// read-write loop. We've already got read data in Buffer.
|
2023-01-09 03:45:20 -05:00
|
|
|
Output->Write(Buffer, sizeof(MagicType) + sizeof(MetadataSizeType), this, OpFileWrite);
|
2022-11-23 03:48:46 -05:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Later clients have a metadata block (TRC2). There's loose support for the
|
|
|
|
|
// future too if need be (TRC[3-9]).
|
|
|
|
|
if (Buffer[0] < '2' || Buffer[0] > '9')
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Concatenate metadata into the buffer, first validating the given size is
|
|
|
|
|
// one that we can handle in a single read.
|
|
|
|
|
uint32 MetadataSize = *(MetadataSizeType*)(Buffer + sizeof(MagicType));
|
|
|
|
|
MetadataSize += sizeof(VersionType);
|
|
|
|
|
if (MetadataSize > BufferSize - uint32(ptrdiff_t(PreambleCursor - Buffer)))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Input.Read(PreambleCursor, MetadataSize, this, OpMetadataRead);
|
|
|
|
|
|
|
|
|
|
return true;
|
2022-11-22 08:02:11 -05:00
|
|
|
}
|
|
|
|
|
|
2021-08-18 07:36:31 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
bool FRecorderRelay::ReadMetadata(int32 Size)
|
|
|
|
|
{
|
2022-11-23 03:48:46 -05:00
|
|
|
// At this point Buffer [magic][md_size][metadata][t_ver][p_ver]
|
|
|
|
|
// looks like this; Buffer--------->PreambleCursor--------->
|
|
|
|
|
// |---------Size---------|
|
|
|
|
|
|
|
|
|
|
// We want to consume [metadata] so some adjustment is required.
|
|
|
|
|
int32 ReadSize = Size - sizeof(VersionType);
|
|
|
|
|
const uint8* Cursor = PreambleCursor;
|
2021-08-18 07:36:31 -04:00
|
|
|
|
|
|
|
|
// MetadataFields
|
2023-01-09 03:44:40 -05:00
|
|
|
while (ReadSize >= 2)
|
2021-08-18 07:36:31 -04:00
|
|
|
{
|
|
|
|
|
struct {
|
|
|
|
|
uint8 Size;
|
|
|
|
|
uint8 Id;
|
|
|
|
|
} MetadataField;
|
|
|
|
|
MetadataField = *(const decltype(MetadataField)*)(Read(sizeof(MetadataField)));
|
|
|
|
|
|
2023-01-09 03:44:40 -05:00
|
|
|
Cursor += sizeof(FMetadataHeader);
|
|
|
|
|
ReadSize -= sizeof(FMetadataHeader);
|
|
|
|
|
|
2022-11-23 03:48:46 -05:00
|
|
|
if (ReadSize < MetadataField.Size)
|
2021-08-18 07:36:31 -04:00
|
|
|
{
|
2022-11-23 03:52:58 -05:00
|
|
|
return false;
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (MetadataField.Id == 0) /* ControlPortFieldId */
|
|
|
|
|
{
|
|
|
|
|
ControlPort = *(const uint16*)Cursor;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-09 03:44:40 -05:00
|
|
|
Cursor += MetadataField.Size;
|
2022-11-23 03:48:46 -05:00
|
|
|
ReadSize -= MetadataField.Size;
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
|
|
|
|
|
2022-11-23 03:53:33 -05:00
|
|
|
// There should be no data left to consume if the metadata was well-formed
|
|
|
|
|
if (ReadSize != 0)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-23 03:48:46 -05:00
|
|
|
// Now we've a full preamble we are ready to write the trace.
|
|
|
|
|
if (!CreateTrace())
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Analysis needs the preamble too.
|
|
|
|
|
uint32 PreambleSize = uint32(ptrdiff_t(PreambleCursor - Buffer)) + Size;
|
|
|
|
|
Output->Write(Buffer, PreambleSize, this, OpFileWrite);
|
|
|
|
|
|
2021-08-18 07:36:31 -04:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
void FRecorderRelay::OnIoComplete(uint32 Id, int32 Size)
|
|
|
|
|
{
|
|
|
|
|
if (Size < 0)
|
|
|
|
|
{
|
|
|
|
|
Close();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (Id)
|
|
|
|
|
{
|
2022-11-23 03:48:46 -05:00
|
|
|
case OpMagicRead:
|
|
|
|
|
if (!ReadMagic())
|
|
|
|
|
{
|
|
|
|
|
Close();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case OpMetadataRead:
|
2021-08-18 07:36:31 -04:00
|
|
|
if (!ReadMetadata(Size))
|
|
|
|
|
{
|
|
|
|
|
Close();
|
|
|
|
|
}
|
2022-11-23 03:48:46 -05:00
|
|
|
break;
|
2021-08-18 07:36:31 -04:00
|
|
|
|
|
|
|
|
case OpSocketRead:
|
|
|
|
|
Output->Write(Buffer, Size, this, OpFileWrite);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case OpFileWrite:
|
2022-11-23 03:48:46 -05:00
|
|
|
Input.ReadSome(Buffer, BufferSize, this, OpSocketRead);
|
2021-08-18 07:36:31 -04:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
uint32 FRecorder::FSession::GetId() const
|
|
|
|
|
{
|
|
|
|
|
return Id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
uint32 FRecorder::FSession::GetTraceId() const
|
|
|
|
|
{
|
2022-11-22 07:39:38 -05:00
|
|
|
return Relay->GetTraceId();
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
uint32 FRecorder::FSession::GetIpAddress() const
|
|
|
|
|
{
|
|
|
|
|
return Relay->GetIpAddress();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
uint32 FRecorder::FSession::GetControlPort() const
|
|
|
|
|
{
|
|
|
|
|
return Relay->GetControlPort();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
FRecorder::FRecorder(asio::io_context& IoContext, FStore& InStore)
|
|
|
|
|
: FAsioTcpServer(IoContext)
|
|
|
|
|
, FAsioTickable(IoContext)
|
|
|
|
|
, Store(InStore)
|
|
|
|
|
{
|
|
|
|
|
StartTick(500);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
FRecorder::~FRecorder()
|
|
|
|
|
{
|
|
|
|
|
check(!FAsioTickable::IsActive());
|
|
|
|
|
check(!FAsioTcpServer::IsOpen());
|
|
|
|
|
|
|
|
|
|
for (FSession& Session : Sessions)
|
|
|
|
|
{
|
|
|
|
|
delete Session.Relay;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
void FRecorder::Close()
|
|
|
|
|
{
|
|
|
|
|
FAsioTickable::StopTick();
|
|
|
|
|
FAsioTcpServer::Close();
|
|
|
|
|
|
|
|
|
|
for (FSession& Session : Sessions)
|
|
|
|
|
{
|
|
|
|
|
Session.Relay->Close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
uint32 FRecorder::GetSessionCount() const
|
|
|
|
|
{
|
|
|
|
|
return uint32(Sessions.Num());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
const FRecorder::FSession* FRecorder::GetSessionInfo(uint32 Index) const
|
|
|
|
|
{
|
|
|
|
|
if (Index >= uint32(Sessions.Num()))
|
|
|
|
|
{
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Sessions.GetData() + Index;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
bool FRecorder::OnAccept(asio::ip::tcp::socket& Socket)
|
|
|
|
|
{
|
2022-11-22 07:39:38 -05:00
|
|
|
auto* Relay = new FRecorderRelay(Socket, Store);
|
2021-08-18 07:36:31 -04:00
|
|
|
|
|
|
|
|
uint32 IdPieces[] = {
|
|
|
|
|
Relay->GetIpAddress(),
|
|
|
|
|
Socket.remote_endpoint().port(),
|
|
|
|
|
Socket.local_endpoint().port(),
|
|
|
|
|
0,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
FSession Session;
|
|
|
|
|
Session.Relay = Relay;
|
|
|
|
|
Session.Id = QuickStoreHash(IdPieces);
|
|
|
|
|
Sessions.Add(Session);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
void FRecorder::OnTick()
|
|
|
|
|
{
|
|
|
|
|
uint32 FinalNum = 0;
|
|
|
|
|
for (int i = 0, n = Sessions.Num(); i < n; ++i)
|
|
|
|
|
{
|
|
|
|
|
FSession& Session = Sessions[i];
|
|
|
|
|
if (Session.Relay->IsOpen())
|
|
|
|
|
{
|
|
|
|
|
Sessions[FinalNum] = Session;
|
|
|
|
|
++FinalNum;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delete Session.Relay;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Sessions.SetNum(FinalNum);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* vim: set noexpandtab : */
|