Project SkyFire Core
SkyFire 5.4.8 server core API documentation
Loading...
Searching...
No Matches
PacketLogServer.cpp
Go to the documentation of this file.
1/*
2* This file is part of Project SkyFire https://www.projectskyfire.org.
3* See LICENSE.md file for Copyright information
4*/
5
6#include "Config.h"
7#include "ByteBuffer.h"
8#include "PacketLogServer.h"
9#include <algorithm>
10#include <cctype>
11#include <chrono>
12#include <cstdint>
13#include <cstdio>
14#include <ctime>
15#include <filesystem>
16#include <iomanip>
17#include <sstream>
18
19namespace
20{
21bool IsAbsolutePath(std::string const& path)
22{
23 if (path.empty())
24 return false;
25
26 if (path[0] == '/' || path[0] == '\\')
27 return true;
28
29 return path.size() > 2 && std::isalpha(static_cast<unsigned char>(path[0])) && path[1] == ':';
30}
31
32std::string JoinPath(std::string base, std::string const& path)
33{
34 if (path.empty() || IsAbsolutePath(path) || base.empty())
35 return path;
36
37 if (base.back() != '/' && base.back() != '\\')
38 base.push_back('/');
39
40 return base + path;
41}
42
43std::string SanitizePathPart(std::string value)
44{
45 for (char& ch : value)
46 if (!std::isalnum(static_cast<unsigned char>(ch)) && ch != '.' && ch != '-' && ch != '_')
47 ch = '_';
48
49 if (value.empty())
50 value = "unknown";
51
52 return value;
53}
54
55std::string SanitizeMarker(std::string marker)
56{
57 for (char& ch : marker)
58 if (ch == '\r' || ch == '\n')
59 ch = ' ';
60
61 return marker;
62}
63
64std::string NormalizeFilterName(std::string value)
65{
66 std::transform(value.begin(), value.end(), value.begin(), [](unsigned char ch)
67 {
68 return static_cast<char>(std::tolower(ch));
69 });
70
71 return value;
72}
73
74std::string GetAccountPathPart(Skyfire::PacketLogServerSessionInfo const& sessionInfo)
75{
76 if (!sessionInfo.AccountName.empty())
77 return sessionInfo.AccountName;
78
79 return std::to_string(sessionInfo.AccountId);
80}
81
82std::string GetSessionPathPart(Skyfire::PacketLogServerSessionInfo const& sessionInfo)
83{
84 if (!sessionInfo.CharacterName.empty())
85 return sessionInfo.CharacterName;
86
87 if (!sessionInfo.SessionName.empty())
88 return sessionInfo.SessionName;
89
90 return "none";
91}
92
93std::string GetFilePrefix(Skyfire::PacketLogServerSessionInfo const& sessionInfo)
94{
95 return sessionInfo.FilePrefix.empty() ? "session" : sessionInfo.FilePrefix;
96}
97
98std::string GetFilterName(Skyfire::PacketLogServerSessionInfo const& sessionInfo)
99{
100 if (!sessionInfo.CharacterName.empty())
101 return sessionInfo.CharacterName;
102
103 return sessionInfo.SessionName;
104}
105
106std::string BuildSessionIdentity(Skyfire::PacketLogServerSessionInfo const& sessionInfo)
107{
108 std::ostringstream identity;
109 identity << GetFilePrefix(sessionInfo)
110 << '|'
111 << sessionInfo.RemoteAddress
112 << '|'
113 << GetAccountPathPart(sessionInfo)
114 << '|'
115 << sessionInfo.RealmId
116 << '|'
117 << GetSessionPathPart(sessionInfo);
118
119 return identity.str();
120}
121
122std::filesystem::path BuildSessionLogPath(std::string const& sessionLogDir, void const* sessionKey,
123 Skyfire::PacketLogServerSessionInfo const& sessionInfo)
124{
125 auto now = std::chrono::system_clock::now();
126 std::time_t timestamp = std::chrono::system_clock::to_time_t(now);
127 std::tm localTime;
128#if PLATFORM == PLATFORM_WINDOWS
129 localtime_s(&localTime, &timestamp);
130#else
131 localtime_r(&timestamp, &localTime);
132#endif
133
134 std::ostringstream filename;
135 filename << GetFilePrefix(sessionInfo)
136 << "_"
137 << std::put_time(&localTime, "%Y%m%d_%H%M%S")
138 << "_"
139 << SanitizePathPart(sessionInfo.RemoteAddress)
140 << "_account"
141 << SanitizePathPart(GetAccountPathPart(sessionInfo))
142 << "_realm"
143 << sessionInfo.RealmId
144 << "_"
145 << SanitizePathPart(GetSessionPathPart(sessionInfo))
146 << "_"
147 << reinterpret_cast<uintptr_t>(sessionKey)
148 << ".pktlog";
149
150 return std::filesystem::path(sessionLogDir) / filename.str();
151}
152}
153
154namespace Skyfire
155{
160
165
167{
168 std::lock_guard<std::mutex> guard(_sessionLock);
170
171 if (_binaryFile)
172 fclose(_binaryFile);
173
174 _binaryFile = NULL;
175}
176
177void PacketLogServer::Initialize(std::string const& controlFileDefault, std::string const& outputDirDefault)
178{
179 std::string logsDir = sConfigMgr->GetStringDefault("LogsDir", "");
180 Configure(
181 sConfigMgr->GetStringDefault("PacketLogServerControlFile", controlFileDefault),
182 sConfigMgr->GetStringDefault("PacketLogServerOutputDir", outputDirDefault),
183 logsDir);
184 ConfigureLegacyPacketLogFile(sConfigMgr->GetStringDefault("PacketLogFile", ""));
185}
186
187void PacketLogServer::Configure(std::string const& controlFile, std::string const& outputDir, std::string logsDir)
188{
189 if (!logsDir.empty())
190 if ((logsDir.at(logsDir.length() - 1) != '/') && (logsDir.at(logsDir.length() - 1) != '\\'))
191 logsDir.push_back('/');
192
193 std::lock_guard<std::mutex> guard(_sessionLock);
194 _logsDir = logsDir;
195 _controlFiles.clear();
196 _sessionLogDir = outputDir.empty() ? "PacketLogs" : outputDir;
197 _globalSessionLogging = false;
200
201 if (!controlFile.empty())
202 {
203 _controlFiles.push_back(controlFile);
204
205 std::string legacyControlFile = JoinPath(_logsDir, controlFile);
206 if (legacyControlFile != controlFile)
207 _controlFiles.push_back(legacyControlFile);
208 }
209}
210
211void PacketLogServer::ConfigureLegacyPacketLogFile(std::string const& packetLogFile)
212{
213 std::lock_guard<std::mutex> guard(_sessionLock);
214
215 if (_binaryFile)
216 {
217 fclose(_binaryFile);
218 _binaryFile = NULL;
219 }
220
221 if (!packetLogFile.empty())
222 _binaryFile = fopen(JoinPath(_logsDir, packetLogFile).c_str(), "wb");
223}
224
226{
227 std::lock_guard<std::mutex> guard(_sessionLock);
229}
230
232{
233 for (std::string const& controlFile : _controlFiles)
234 {
235 std::error_code error;
236 if (!controlFile.empty() && std::filesystem::exists(controlFile, error) && !error)
237 return true;
238 }
239
240 return false;
241}
242
244{
245 std::lock_guard<std::mutex> guard(_sessionLock);
247}
248
250{
251 std::lock_guard<std::mutex> guard(_sessionLock);
252 _globalSessionLogging = false;
254
255 for (std::string const& controlFile : _controlFiles)
256 {
257 std::error_code error;
258 std::filesystem::remove(controlFile, error);
259 }
260
262}
263
264void PacketLogServer::EnableCharacterLogging(std::string const& characterName)
265{
266 std::lock_guard<std::mutex> guard(_sessionLock);
267 _characterSessionLogging.insert(NormalizeFilterName(characterName));
268}
269
270void PacketLogServer::DisableCharacterLogging(std::string const& characterName)
271{
272 std::lock_guard<std::mutex> guard(_sessionLock);
273 std::string normalizedName = NormalizeFilterName(characterName);
274 _characterSessionLogging.erase(normalizedName);
275
277 CloseCharacterSessions(normalizedName);
278}
279
281{
282 std::lock_guard<std::mutex> guard(_sessionLock);
284}
285
287{
288 std::lock_guard<std::mutex> guard(_sessionLock);
289 return _characterSessionLogging.size();
290}
291
293{
295 return true;
296
297 std::string filterName = GetFilterName(sessionInfo);
298 if (filterName.empty())
299 return false;
300
301 return _characterSessionLogging.find(NormalizeFilterName(filterName)) != _characterSessionLogging.end();
302}
303
304FILE* PacketLogServer::OpenSessionLog(void const* sessionKey, PacketLogServerSessionInfo const& sessionInfo)
305{
306 auto itr = _sessionLogs.find(sessionKey);
307 if (itr != _sessionLogs.end())
308 return itr->second.file;
309
310 std::error_code error;
311 std::filesystem::create_directories(_sessionLogDir, error);
312 if (error)
313 return NULL;
314
315 std::filesystem::path path = BuildSessionLogPath(_sessionLogDir, sessionKey, sessionInfo);
316 FILE* file = fopen(path.string().c_str(), "w");
317 if (!file)
318 return NULL;
319
320 fprintf(file, "# SkyFire packet log session\n");
321 fprintf(file, "# RemoteAddress: %s\n", sessionInfo.RemoteAddress.c_str());
322 fprintf(file, "# AccountId: %u\n", sessionInfo.AccountId);
323 if (!sessionInfo.AccountName.empty())
324 fprintf(file, "# AccountName: %s\n", sessionInfo.AccountName.c_str());
325 fprintf(file, "# RealmId: %u\n", sessionInfo.RealmId);
326 fprintf(file, "# CharacterName: %s\n", sessionInfo.CharacterName.empty() ? "none" : sessionInfo.CharacterName.c_str());
327 if (!sessionInfo.SessionName.empty())
328 fprintf(file, "# SessionName: %s\n", sessionInfo.SessionName.c_str());
329 fprintf(file, "# Format: sequence unix_time direction opcode_number opcode_name size payload_hex\n");
330 fflush(file);
331
332 SessionLog session;
333 session.file = file;
334 session.sequence = 0;
335 session.path = path.string();
336 session.info = sessionInfo;
337 _sessionLogs[sessionKey] = session;
338 return file;
339}
340
341void PacketLogServer::LogPacket(void const* sessionKey, PacketLogServerSessionInfo const& sessionInfo,
342 PacketLogServerDirection direction, uint32 opcode, std::string const& opcodeName,
343 void const* payload, size_t payloadSize)
344{
345 std::lock_guard<std::mutex> guard(_sessionLock);
346 if (!ShouldLogSession(sessionInfo))
347 {
348 auto itr = _sessionLogs.find(sessionKey);
349 if (itr != _sessionLogs.end())
350 {
351 if (itr->second.file)
352 fclose(itr->second.file);
353
354 _sessionLogs.erase(itr);
355 }
356
357 return;
358 }
359
360 FILE* file = OpenSessionLog(sessionKey, sessionInfo);
361 if (!file)
362 return;
363
364 SessionLog& session = _sessionLogs[sessionKey];
365 session.info = sessionInfo;
366
367 fprintf(file, "%llu %u %s 0x%04X %s %u ",
368 static_cast<unsigned long long>(++session.sequence),
369 static_cast<uint32>(time(NULL)),
370 direction == PACKET_LOG_CLIENT_TO_SERVER ? "CMSG" : "SMSG",
371 opcode,
372 opcodeName.c_str(),
373 static_cast<uint32>(payloadSize));
374
375 uint8 const* bytes = static_cast<uint8 const*>(payload);
376 for (size_t i = 0; i < payloadSize && bytes; ++i)
377 fprintf(file, "%02X", bytes[i]);
378
379 fputc('\n', file);
380 fflush(file);
381}
382
383void PacketLogServer::LogBinaryPacket(PacketLogServerDirection direction, uint32 opcode, void const* payload, size_t payloadSize)
384{
385 std::lock_guard<std::mutex> guard(_sessionLock);
386 if (!_binaryFile)
387 return;
388
389 ByteBuffer data(uint32(4 + 4 + 4 + 1 + payloadSize));
390 data << int32(opcode);
391 data << int32(payloadSize);
392 data << uint32(time(NULL));
393 data << uint8(direction == PACKET_LOG_CLIENT_TO_SERVER ? 0 : 1);
394
395 if (payload && payloadSize)
396 data.append(static_cast<uint8 const*>(payload), payloadSize);
397
398 fwrite(data.contents(), 1, data.size(), _binaryFile);
399 fflush(_binaryFile);
400}
401
402void PacketLogServer::LogMarker(void const* sessionKey, PacketLogServerSessionInfo const& sessionInfo, std::string const& marker)
403{
404 std::lock_guard<std::mutex> guard(_sessionLock);
405 if (!ShouldLogSession(sessionInfo))
406 {
407 auto itr = _sessionLogs.find(sessionKey);
408 if (itr != _sessionLogs.end())
409 {
410 if (itr->second.file)
411 fclose(itr->second.file);
412
413 _sessionLogs.erase(itr);
414 }
415
416 return;
417 }
418
419 FILE* file = OpenSessionLog(sessionKey, sessionInfo);
420 if (!file)
421 return;
422
423 SessionLog& session = _sessionLogs[sessionKey];
424 session.info = sessionInfo;
425 fprintf(file, "# %llu %u MARKER %s\n",
426 static_cast<unsigned long long>(++session.sequence),
427 static_cast<uint32>(time(NULL)),
428 SanitizeMarker(marker).c_str());
429 fflush(file);
430}
431
432void PacketLogServer::RefreshSessionInfo(void const* sessionKey, PacketLogServerSessionInfo const& sessionInfo)
433{
434 std::lock_guard<std::mutex> guard(_sessionLock);
435 auto itr = _sessionLogs.find(sessionKey);
436 if (itr == _sessionLogs.end())
437 return;
438
439 SessionLog& session = itr->second;
440 bool const identityChanged = BuildSessionIdentity(session.info) != BuildSessionIdentity(sessionInfo);
441 session.info = sessionInfo;
442
443 if (!identityChanged || session.path.empty())
444 return;
445
446 if (session.file)
447 {
448 fflush(session.file);
449 fclose(session.file);
450 session.file = NULL;
451 }
452
453 std::filesystem::path oldPath = session.path;
454 std::filesystem::path newPath = BuildSessionLogPath(_sessionLogDir, sessionKey, sessionInfo);
455 std::error_code error;
456 std::filesystem::rename(oldPath, newPath, error);
457 if (!error)
458 session.path = newPath.string();
459
460 session.file = fopen(session.path.c_str(), "a");
461 if (!session.file)
462 {
463 _sessionLogs.erase(itr);
464 return;
465 }
466
467 fprintf(session.file, "# %llu %u MARKER refreshed session metadata accountId=%u accountName=%s realm=%u character=%s session=%s\n",
468 static_cast<unsigned long long>(++session.sequence),
469 static_cast<uint32>(time(NULL)),
470 sessionInfo.AccountId,
471 sessionInfo.AccountName.empty() ? "none" : sessionInfo.AccountName.c_str(),
472 sessionInfo.RealmId,
473 sessionInfo.CharacterName.empty() ? "none" : sessionInfo.CharacterName.c_str(),
474 sessionInfo.SessionName.empty() ? "none" : sessionInfo.SessionName.c_str());
475 fflush(session.file);
476}
477
478void PacketLogServer::CloseSession(void const* sessionKey)
479{
480 std::lock_guard<std::mutex> guard(_sessionLock);
481 auto itr = _sessionLogs.find(sessionKey);
482 if (itr == _sessionLogs.end())
483 return;
484
485 if (itr->second.file)
486 fclose(itr->second.file);
487
488 _sessionLogs.erase(itr);
489}
490
492{
493 for (auto& session : _sessionLogs)
494 if (session.second.file)
495 fclose(session.second.file);
496
497 _sessionLogs.clear();
498}
499
500void PacketLogServer::CloseCharacterSessions(std::string const& normalizedName)
501{
502 for (auto itr = _sessionLogs.begin(); itr != _sessionLogs.end();)
503 {
504 if (NormalizeFilterName(GetFilterName(itr->second.info)) != normalizedName)
505 {
506 ++itr;
507 continue;
508 }
509
510 if (itr->second.file)
511 fclose(itr->second.file);
512
513 itr = _sessionLogs.erase(itr);
514 }
515}
516}
#define sConfigMgr
Definition Config.h:64
std::int32_t int32
Definition Define.h:73
std::uint8_t uint8
Definition Define.h:79
std::uint32_t uint32
Definition Define.h:77
void append(T value)
Definition ByteBuffer.h:147
size_t size() const
Definition ByteBuffer.h:609
uint8 * contents()
Definition ByteBuffer.h:605
void EnableCharacterLogging(std::string const &characterName)
void Initialize(std::string const &controlFileDefault, std::string const &outputDirDefault)
std::vector< std::string > _controlFiles
void LogBinaryPacket(PacketLogServerDirection direction, uint32 opcode, void const *payload, size_t payloadSize)
void DisableCharacterLogging(std::string const &characterName)
std::unordered_set< std::string > _characterSessionLogging
bool IsControlFileLoggingEnabled() const
void CloseSession(void const *sessionKey)
void LogPacket(void const *sessionKey, PacketLogServerSessionInfo const &sessionInfo, PacketLogServerDirection direction, uint32 opcode, std::string const &opcodeName, void const *payload, size_t payloadSize)
void Configure(std::string const &controlFile, std::string const &outputDir, std::string logsDir="")
void RefreshSessionInfo(void const *sessionKey, PacketLogServerSessionInfo const &sessionInfo)
size_t GetCharacterLoggingCount() const
void CloseCharacterSessions(std::string const &normalizedName)
std::unordered_map< void const *, SessionLog > _sessionLogs
void LogMarker(void const *sessionKey, PacketLogServerSessionInfo const &sessionInfo, std::string const &marker)
void ConfigureLegacyPacketLogFile(std::string const &packetLogFile)
FILE * OpenSessionLog(void const *sessionKey, PacketLogServerSessionInfo const &sessionInfo)
bool ShouldLogSession(PacketLogServerSessionInfo const &sessionInfo) const
@ PACKET_LOG_CLIENT_TO_SERVER
PacketLogServerSessionInfo info