Skip to content

Commit e2bb123

Browse files
committed
docs: adding page on new error rate limit
1 parent 24ae573 commit e2bb123

1 file changed

Lines changed: 212 additions & 0 deletions

File tree

doc/docs/guides/error-handling.md

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
---
2+
sidebar_position: 15
3+
---
4+
# Error Handling
5+
6+
Mirage includes a robust error handling system to manage issues caused by players, such as invalid messages or exploits. This system helps protect your server from crashes and allows for custom logic to handle problematic players.
7+
8+
## Overview
9+
10+
The server tracks errors caused by player actions, like malformed RPCs. If a player accumulates too many errors within a certain timeframe, they can be automatically disconnected or handled by a custom callback.
11+
12+
:::note
13+
This rate-limiting system is server-side only and replaces the old `DisconnectOnException` functionality on the server. The client still has a simple `DisconnectOnException` toggle for its own error handling.
14+
:::
15+
16+
## Player Error Flags
17+
18+
The `PlayerErrorFlags` enum helps categorize the types of errors a player can cause, allowing for more granular tracking and response.
19+
20+
```csharp
21+
// see PlayerErrorFlags in the source code for most up-to-date values
22+
[Flags]
23+
public enum PlayerErrorFlags
24+
{
25+
None = 0,
26+
27+
// Likely developer bugs
28+
RpcNullException = 1 << 0,
29+
RpcException = 1 << 1,
30+
31+
// Connection/versioning issues
32+
DeserializationException = 1 << 2,
33+
RpcSync = 1 << 3,
34+
RateLimit = 1 << 4,
35+
36+
// Security/Malicious Intent
37+
Unauthorized = 1 << 5,
38+
Critical = 1 << 6,
39+
LikelyCheater = 1 << 7,
40+
41+
// Custom developer defined errors
42+
CustomError = 1 << 16
43+
}
44+
```
45+
46+
You can use these flags to identify an error's cause when implementing custom logic. You can also define your own flags using the `CustomError` bit as a starting point:
47+
48+
```csharp
49+
public static class MyErrorFlags
50+
{
51+
public const PlayerErrorFlags InvalidTrade = PlayerErrorFlags.CustomError << 0;
52+
public const PlayerErrorFlags AnotherCustom = PlayerErrorFlags.CustomError << 1;
53+
}
54+
```
55+
56+
## Server Configuration
57+
58+
### How Error Rate Limiting Works
59+
60+
Mirage uses a token bucket algorithm to manage player errors. Each player has a "bucket" of tokens that represents their error budget.
61+
62+
* **Tokens**: When a player causes an error, a `cost` is deducted from their token bucket.
63+
* **Max Tokens**: The capacity of the bucket. A player starts with this many tokens and cannot exceed this limit. A higher limit allows for a burst of errors, for example if something goes wrong in the game for a short amount of time.
64+
65+
* **Refill & Interval**: Tokens are replenished over time. `Refill` specifies how many tokens are restored every `Interval` (in seconds).
66+
* **Cost**: Represents the severity of an error. When an error occurs, this amount is subtracted from the player's tokens.
67+
* **Reaching the Limit**: If a player's token count drops below zero, they have exhausted their budget. This triggers the error handling logic, which is a disconnect by default.
68+
69+
This system tolerates occasional minor errors while penalizing frequent or severe infractions.
70+
71+
On the `NetworkServer` component, you can configure this behavior:
72+
73+
- **Error Rate Limit Enabled**: Toggles the rate-limiting feature. Enabled by default.
74+
- **Error Rate Limit Config**: Configures the token bucket (`MaxTokens`, `Refill`, `Interval`).
75+
- **Rethrow Exception**: If enabled, exceptions are re-thrown after being logged. This is for debugging and can interrupt server operations.
76+
77+
## Manual Error Reporting
78+
79+
You can manually trigger an error for a player from server-side code using `INetworkPlayer.SetError`.
80+
81+
The `cost` parameter specifies how many tokens to subtract from the player's error bucket. A higher cost leads to faster rate-limiting. Setting a cost higher than the player's current tokens (or even `MaxTokens`) will trigger the error limit immediately.
82+
83+
### Custom Error Example
84+
85+
```csharp
86+
public static class MyErrorFlags
87+
{
88+
public const PlayerErrorFlags InvalidAction = PlayerErrorFlags.CustomError << 0;
89+
}
90+
91+
// ... inside a NetworkBehaviour
92+
[ServerRpc]
93+
void CmdDoSomething(int data)
94+
{
95+
// The IsActionValid method would contain your custom validation logic.
96+
if (!IsActionValid(data))
97+
{
98+
// Penalize the player with a moderate cost for sending invalid data.
99+
Owner.SetError(10, MyErrorFlags.InvalidAction);
100+
return;
101+
}
102+
103+
// ... process valid data
104+
}
105+
```
106+
107+
### Critical Error Example
108+
109+
For severe violations, use `PlayerErrorFlags.Critical` with a high cost to trigger the handler instantly.
110+
111+
```csharp
112+
[ServerRpc]
113+
void CmdTryAdminAction(string command)
114+
{
115+
// The IsAdmin method would check if the player has admin privileges.
116+
if (!IsAdmin(Owner))
117+
{
118+
// A non-admin tried to use an admin command.
119+
// Set cost higher than MaxTokens (default 200) to trigger the limit immediately.
120+
Owner.SetError(10000, PlayerErrorFlags.Critical);
121+
return;
122+
}
123+
124+
// ... execute admin command
125+
}
126+
```
127+
128+
### ServerRpc Without Authority (with Sender)
129+
130+
Sometimes you need a `ServerRpc` to be callable from any client, not just the owner of the `NetworkBehaviour`, and you need to know which client sent the RPC. Use `requireAuthority = false` and include `INetworkPlayer sender = null` as a parameter.
131+
132+
```csharp
133+
[Client]
134+
public void SendPublicMessage(string message)
135+
{
136+
// client side check before sending message
137+
if (string.IsNullOrWhiteSpace(message) || message.Length > 100)
138+
return;
139+
140+
CmdSendPublicMessage(message)
141+
}
142+
143+
[ServerRpc(requireAuthority = false)]
144+
void CmdSendPublicMessage(string message, INetworkPlayer sender = null)
145+
{
146+
if (string.IsNullOrWhiteSpace(message) || message.Length > 100)
147+
{
148+
// Invalid message length. this is very likely a cheat because message length is checked on client before
149+
// how ever this is just chat message nothing not critical gameplay
150+
// for example could be from chat mod with higher size that they left on after playing on a modded server
151+
sender.SetError(50, PlayerErrorFlags.LikelyCheater);
152+
return;
153+
}
154+
155+
if (CheckMessageRateLimit(sender))
156+
{
157+
// player sent more message than chat rate limit, just use low cost
158+
sender.SetError(1, PlayerErrorFlags.None);
159+
return;
160+
}
161+
162+
// ...
163+
}
164+
165+
166+
## Custom Error Handling
167+
168+
Instead of the default disconnect, you can define a custom callback to execute when a player reaches their error limit using `NetworkServer.SetErrorRateLimitReachedCallback`.
169+
170+
This callback is best used alongside `NetworkAuthenticator` so that you can ban or timeout users, stopping them from reconnecting.
171+
172+
You can check `player.ErrorFlags` to see how important the errors have been.
173+
174+
```csharp
175+
using Mirage;
176+
using UnityEngine;
177+
178+
public class MyGameServer : MonoBehaviour
179+
{
180+
public NetworkServer server;
181+
182+
void Start()
183+
{
184+
server.SetErrorRateLimitReachedCallback(OnPlayerErrorLimitReached);
185+
}
186+
187+
void OnPlayerErrorLimitReached(INetworkPlayer player)
188+
{
189+
Debug.LogWarning($"Player {player} reached error limit with flags: {player.ErrorFlags}");
190+
191+
if ((player.ErrorFlags & PlayerErrorFlags.Critical) != 0)
192+
{
193+
// For critical errors, always disconnect.
194+
player.Disconnect();
195+
196+
// ... add player to ban or timeout list here so they can't reconnect
197+
198+
return;
199+
}
200+
else if ((player.ErrorFlags & MyErrorFlags.InvalidAction) != 0)
201+
{
202+
// For our custom action, maybe just send a warning.
203+
// Note: You would need to implement the ChatMessage struct and its handler.
204+
// player.Send(new ChatMessage("You are performing too many invalid actions."));
205+
}
206+
// ... other custom logic
207+
208+
// Reset flags after handling
209+
player.ResetErrorFlag();
210+
}
211+
}
212+
```

0 commit comments

Comments
 (0)