Archive the traffic of observed accounts - #917
Merged
Merged
Conversation
This was referenced Aug 29, 2026
An account can be marked with the new flag IsNetworkObservationActive. While it's set, the game server archives the traffic of each session of that account on the file system, so that it can be analyzed later - for example when a player is suspected of cheating. The archive is a feature of the game server, not of the admin panel: it's registered in the all-in-one host and in the Dapr game server host, so an observed account is archived even when no admin panel is running. * A session is a directory below the one of its account, which holds the packets in one or more files of the analyzer tool format plus the metadata as json. That way a session can be opened, copied or deleted as a whole - and each of its files can be loaded by the WinForms tool. * The packets are written by an own task and flushed as soon as its queue ran empty, so that the network thread never waits for the file system, a running session can be read, and a crash doesn't lose everything. * Each line carries a sequence number as a fifth field, which the loader of the analyzer tool ignores. It makes a gap visible - the writer drops packets rather than slowing the connection of the player down when the file system can't keep up. * Rotation, quota and retention keep the archive bounded. The housekeeping runs when a session starts and when one ends; a running session is never removed. * The archive starts when the player is logged in, so the version check and the login request are not part of it. Capturing them would mean to capture every connection unconditionally, which is exactly what this feature avoids. The archive path, the rotation size, the quota, the retention and the live buffer size of the analyzer page are new properties of the system configuration, so they can be edited in the admin panel. Player got a PlayerLoggedIn event, raised by the new SetAccountAsync: at the moment the state machine reaches "Authenticated", the account of the player isn't assigned yet, so the state change is not the right moment to decide whether a session has to be archived. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pb82LmoaUVdZtBtQs7xrtA
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pb82LmoaUVdZtBtQs7xrtA
sven-n
force-pushed
the
claude/network-observation-archive
branch
from
September 4, 2026 19:16
8ca7c49 to
67bb727
Compare
sven-n
pushed a commit
that referenced
this pull request
Sep 4, 2026
The only conflict was in NetworkObservationHandler, which arrived on master with the merge of #917: the observation toggle of this branch added ApplyObservationAsync and split StopSessionAsync out of the disconnect handler, so both additions are kept on top of the version of master. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pb82LmoaUVdZtBtQs7xrtA
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 5 of #895 — the observation flag, the session archive and its settings. It doesn't touch anything of phase 4 (#916), so the two are independent.
An account can be marked with the new
Account.IsNetworkObservationActive. While it's set, the game server archives the traffic of every session of that account on the file system. The flag renders in the account editor throughAutoForm, so there is no new page for it.Where it runs
The archive belongs to the game server, not to the admin panel (decision 9 of the issue): it's registered in the all-in-one host and in
Dapr/GameServer.Host, so an observed account is archived even when no admin panel is running anywhere.GameServertakes an optionalIPacketArchive; without one, nothing changes at all.What a session looks like
A session is a directory rather than a single file — the plan sketched
<account>/<date>_<server>.mucapplus a sidecar, but with rotation a session has more than one file, and a directory keeps "open it", "copy it", "delete it" and "measure it" simple. Each part is a.mucapin the format of the WinForms tool, complete with its own start-timestamp header, so a part can be opened there directly.session.jsoncarries what the capture format has no place for: account, character names (added when the player enters the world), server type/id/description, remote endpoint, client version, start/end time, packet count, dropped packet count and the list of parts.Each packet line gets the sequence number as a fifth field. The existing loader reads fields 0..3 and ignores the rest, so the files stay backwards compatible. It's what makes a gap visible: when the file system can't keep up, the writer drops packets instead of slowing the player's connection down (the count ends up in the metadata).
Writing
The packets go through a bounded channel into an own task, which appends lines and flushes as soon as the queue ran empty. So the network thread never waits for the file system, a session is readable while it runs, and a crashed process leaves everything up to the last flush. Reader and writer share the files (
FileShare.ReadWrite | FileShare.Delete), and a half-written last line is skipped rather than throwing.The archive starts at login, not at connect: the version check and the login request are not part of it. Capturing them would mean capturing every connection unconditionally — exactly what this feature avoids (also stated in the issue).
Playergot aPlayerLoggedInevent, raised by a newSetAccountAsync. The state machine reachesAuthenticatedbeforeLoginActionassignsplayer.Account, so the state change is not the right moment to decide whether a session has to be archived — the account isn't there yet.Housekeeping
Rotation (per file), quota (whole archive, oldest first) and retention (age) keep it bounded. It runs when a session starts and when one ends; a session which is currently being written is never removed.
Settings
SystemConfigurationgained five properties, editable in the admin panel like the existing ones:NetworkAnalyzerLiveBufferSizeNetworkObservationArchivePathcapturesNetworkObservationMaxSessionSizeMbNetworkObservationMaxTotalSizeMbNetworkObservationRetentionDaysThe live buffer size is now used by the capture service of phase 2 instead of its hard-coded default.
One migration covers the five properties and the account flag. It was generated with
dotnet ef migrations add; the only hand edit is thedefaultValueof each new column, so that an existing database gets the documented defaults instead of0(which would mean "no quota, no retention"). A value of0is still read as "unlimited"/"default" at runtime, so a database which was updated by other means behaves sensibly too.Deviations worth knowing
Data protection
Starting a session, finishing it and deleting one are logged at information level, so observing a player leaves a trace. The default archive path (
captures, next to the binaries) is outside anything served statically, andcaptures/was added to.gitignore.Tests
PacketArchiveTest(14): round trip of the captured packets and their direction, reading a session while it's being written, rotation into several files, the newest-packets cap, filtering by account, delete, "a running session is not deleted", an id which points outside the archive, retention, quota, a sanitized account name, an incomplete last line, and the metadata being readable as json.NetworkObservationTests(5): the traffic of an observed account is archived and the archive is a sink of the connection; an unobserved account is not touched; the session is finished when the player disconnects; and the mapping of the system configuration to the options, including the fallbacks. The disconnect test found a real bug on the way: the sink was read back fromRemotePlayer.Connection, which is already gone during the teardown, so the session kept its sink. The connection is part of the session state now.dotnet build src/MUnique.OpenMU.sln -p:ci=true→ 0 errors, no warning from a file this PR touches.MUnique.OpenMU.Tests807,Network.Tests73 (4 skipped),Network.Packets.Tests588,Web.Tests63,ChatServer.Tests26,PlugIns.Tests41,Persistence.Initialization.Tests12 (2 skipped),AttributeSystem.Tests44,Pathfinding.Tests9.Not verified against a real PostgreSQL database or a running server — that's yours, as agreed.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Pb82LmoaUVdZtBtQs7xrtA
Generated by Claude Code