mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
feat(capture): record whole packets of an account, a character or everything on every server
Staff arm a packet capture on the dashboard; master passes MESSAGE_CAPTURE_CONTROL ARM to every world, auth and chat and arms its own. Each server's PacketCapture tap (dServer receive, and a send hook in RakPeer::Send so replica constructions are seen too) records into one preallocated chunk per server and ships sealed chunks through master on the main loop when capture_flush_bytes or capture_flush_interval_ms is reached; past capture_buffer_max_mb the oldest chunks are dropped and the dashboard records a gap. Nothing is armed: one flag check. - targets: an account (from its login; packets before the login are kept per connection and added once auth or the world knows whose they are), a character (from when it is picked), or everything; up to 8 at once (a bit each in the record mask) - worlds and auth record their clients' packets and the master link messages of a captured player (session keys by name, zone transfers by request, player added/removed, migration); chat finds the player in each packet; master records server traffic for everything - secrets are never recorded: structs that carry them (login request, login response user key, world validation session key, session key messages between servers) are read, blanked and written again before recording; auth keeps only the handshake and login - PacketDecoder: a registry by service and message id names every packet and decodes the registered structs; CaptureBundle is the file format (DLUBNDL1, metadata, records); CaptureTools orders records on one timeline, pulls movement out, makes bundles portable or anonymous and diffs replays - the dashboard keeps packet captures in message_capture_sessions (capture_kind 1) and their packets in a file under capture_dir, one write per batch; arming is audited - MESSAGE_CAPTURE_CONTROL/DATA only gain appended enum values and trailing fields Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
104
dNet/PacketCapture.h
Normal file
104
dNet/PacketCapture.h
Normal file
@@ -0,0 +1,104 @@
|
||||
#ifndef __PACKETCAPTURE__H__
|
||||
#define __PACKETCAPTURE__H__
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include "dCommonVars.h"
|
||||
#include "PacketRecord.h"
|
||||
|
||||
class RakPeerInterface;
|
||||
struct MessageCaptureControl;
|
||||
struct MessageCaptureData;
|
||||
struct Packet;
|
||||
struct SystemAddress;
|
||||
enum class ServiceType : uint16_t;
|
||||
|
||||
/**
|
||||
* This server's side of the dashboard's packet capture (docs/CaptureReplay.md): records whole packets, as they went
|
||||
* over RakNet, for up to MessageCapture::MAX_SLOTS armed captures at once, and ships them to the dashboard in batches.
|
||||
*
|
||||
* Every server runs one (auth, chat, world and master): the dashboard arms a capture with MESSAGE_CAPTURE_CONTROL
|
||||
* ARM, master passes it to every server, and each records its part:
|
||||
* - auth and world: what their clients send and receive (world: everything, replica constructions included, since
|
||||
* the tap sits in RakPeer::Send), plus their own master link messages that belong to a captured player
|
||||
* (session keys, zone transfers, player added/removed, instance migration);
|
||||
* - chat: what worlds route to and from it (the player is the object ID each chat packet starts with);
|
||||
* - master: for an EVERYTHING capture, all server-to-server traffic except the dashboard's.
|
||||
*
|
||||
* Nothing is written to disk here and nothing is sent per packet: records are appended to one preallocated chunk,
|
||||
* which is sealed when it reaches capture_flush_bytes or capture_flush_interval_ms has passed, and sent from the
|
||||
* main loop (Update). Sealed chunks waiting to be sent (master unreachable) are capped at capture_buffer_max_mb; the
|
||||
* oldest are dropped past that and counted, so the dashboard shows a gap. With nothing armed the cost is one flag
|
||||
* check per received packet and one null check per sent one.
|
||||
*
|
||||
* RakNet isn't thread safe: everything here runs on the server's main thread (sends from the main thread, receives
|
||||
* where the main loop reads them).
|
||||
*/
|
||||
namespace PacketCapture {
|
||||
extern bool g_Armed;
|
||||
extern bool g_Tracking; // connections are bound (they are forgotten when they close, armed or not)
|
||||
|
||||
// dServer: which peer is the listening one and which the link to master (nullptr on master itself)
|
||||
void Attach(ServiceType serverType, RakPeerInterface* peer, RakPeerInterface* masterLink, uint32_t zoneId, uint32_t instanceId);
|
||||
void Detach();
|
||||
|
||||
// Worlds: their clone (a property's owner), once known
|
||||
void SetClone(uint32_t cloneId);
|
||||
|
||||
// Master: packets to and from the dashboard aren't recorded
|
||||
void IgnorePeer(const SystemAddress& address);
|
||||
|
||||
// ARM or DISARM from the dashboard (START/STOP are the game message inspector's)
|
||||
void Control(const MessageCaptureControl& control);
|
||||
|
||||
// A packet the listening peer received / nullptr when there are no more this frame (dServer::Receive)
|
||||
void OnReceiveImpl(const Packet* packet);
|
||||
inline void OnReceive(const Packet* packet) { if (g_Armed || g_Tracking) OnReceiveImpl(packet); }
|
||||
|
||||
// A packet from master (dServer::ReceiveFromMaster)
|
||||
void OnReceiveFromMasterImpl(const Packet* packet);
|
||||
inline void OnReceiveFromMaster(const Packet* packet) { if (g_Armed) OnReceiveFromMasterImpl(packet); }
|
||||
|
||||
// Who is on the other end of a connection: auth when an account logs in (the account may not exist: 0), worlds
|
||||
// when a session is validated, and the character when one is picked
|
||||
void Bind(const SystemAddress& address, uint32_t accountId, const std::string& accountName);
|
||||
void BindCharacter(const SystemAddress& address, LWOOBJID characterId);
|
||||
|
||||
// Main loop: seals the chunk when it is due, sends sealed chunks, ends captures past their time
|
||||
void Update();
|
||||
|
||||
/**
|
||||
* Where sealed chunks go; returns false when they can't be sent right now (they are kept, up to the cap). The
|
||||
* default sends them to master; master sends them straight to the dashboard.
|
||||
*/
|
||||
using Sink = std::function<bool(MessageCaptureData& data)>;
|
||||
void SetSink(Sink sink);
|
||||
|
||||
struct Settings {
|
||||
uint32_t flushIntervalMs{ 1000 };
|
||||
uint32_t flushBytes{ 256 * 1024 };
|
||||
uint64_t maxBufferBytes{ 16ull * 1024 * 1024 };
|
||||
};
|
||||
// Read from capture_flush_interval_ms, capture_flush_bytes and capture_buffer_max_mb when a capture is armed
|
||||
void SetSettings(const Settings& settings);
|
||||
|
||||
struct Stats {
|
||||
uint64_t recorded{}; // records kept
|
||||
uint64_t recordedBytes{}; // their size, headers included
|
||||
uint64_t dropped{}; // records dropped over the buffer cap
|
||||
uint64_t batches{}; // chunks sent
|
||||
uint64_t sentBytes{};
|
||||
};
|
||||
const Stats& GetStats();
|
||||
|
||||
// Tests: drop every capture, binding and buffer (the attachment stays)
|
||||
void Reset();
|
||||
|
||||
// Tests and tools: record as if the listening peer had sent (broadcast: to all but `address`) or received this
|
||||
void RecordForTest(const SystemAddress& address, bool sent, bool broadcast, const unsigned char* data, uint32_t bits);
|
||||
}
|
||||
|
||||
#endif //!__PACKETCAPTURE__H__
|
||||
Reference in New Issue
Block a user