Skip to content

Commit 7d066a1

Browse files
committed
Refactored Bad Actor logic
1 parent cd68a26 commit 7d066a1

File tree

7 files changed

+183
-440
lines changed

7 files changed

+183
-440
lines changed

Zolian.GameServer/Zolian.GameServer.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
</PropertyGroup>
4040

4141
<ItemGroup>
42-
<PackageReference Include="Chaos-Networking" Version="2.4.0" />
42+
<PackageReference Include="Chaos-Networking" Version="2.4.1" />
4343
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.0-preview3.24332.3" />
4444
<PackageReference Include="Microsoft.DependencyValidation.Analyzers" Version="0.11.0" />
4545
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0">
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
using System.Net;
2+
3+
using Darkages.Models;
4+
5+
using Microsoft.Extensions.Logging;
6+
7+
using Newtonsoft.Json;
8+
9+
using RestSharp;
10+
11+
using ServiceStack;
12+
13+
namespace Darkages.Network.Server;
14+
15+
public static class BadActor
16+
{
17+
private const string InternalIP = "192.168.50.1"; // Cannot use ServerConfig due to value needing to be constant
18+
19+
public static bool ClientOnBlackList(string remoteIp)
20+
{
21+
if (remoteIp.IsNullOrEmpty() || !IPAddress.TryParse(remoteIp, out _)) return true;
22+
if (remoteIp is "127.0.0.1" or InternalIP) return false;
23+
24+
try
25+
{
26+
var keyCode = ServerSetup.Instance.KeyCode;
27+
if (keyCode is null || keyCode.Length == 0)
28+
{
29+
ServerSetup.ConnectionLogger("Keycode not valid or not set within ServerConfig.json");
30+
return false;
31+
}
32+
33+
// BLACKLIST check
34+
var request = new RestRequest("", Method.Get);
35+
request.AddHeader("Key", keyCode);
36+
request.AddHeader("Accept", "application/json");
37+
request.AddParameter("ipAddress", remoteIp);
38+
request.AddParameter("maxAgeInDays", "90");
39+
request.AddParameter("verbose", "");
40+
var response = ExecuteWithRetry(() => ServerSetup.Instance.RestClient.Execute<Ipdb>(request));
41+
42+
if (response?.IsSuccessful == true)
43+
{
44+
var ipdb = JsonConvert.DeserializeObject<Ipdb>(response.Content!);
45+
var abuseConfidenceScore = ipdb?.Data?.AbuseConfidenceScore;
46+
var tor = ipdb?.Data?.IsTor;
47+
var usageType = ipdb?.Data?.UsageType;
48+
49+
if (tor == true)
50+
{
51+
LogBadActor(remoteIp, "using tor");
52+
return true;
53+
}
54+
55+
// Block if known malicious usage type
56+
if (IsMaliciousUsageType(usageType))
57+
{
58+
LogBadActor(remoteIp, $"using {usageType}");
59+
return true;
60+
}
61+
62+
// Block based on abuse confidence score
63+
if (abuseConfidenceScore >= 5)
64+
{
65+
LogBadActor(remoteIp, $"high risk score of {abuseConfidenceScore}");
66+
return true;
67+
}
68+
}
69+
else
70+
{
71+
ServerSetup.ConnectionLogger($"{remoteIp} - API Issue or Failed response");
72+
}
73+
}
74+
catch (Exception ex)
75+
{
76+
ServerSetup.ConnectionLogger($"Error checking blacklist for {remoteIp}: {ex.Message}", LogLevel.Warning);
77+
SentrySdk.CaptureException(ex);
78+
}
79+
80+
return true;
81+
}
82+
83+
private static T ExecuteWithRetry<T>(Func<T> operation, int maxRetries = 3)
84+
{
85+
var attempt = 0;
86+
87+
while (attempt < maxRetries)
88+
{
89+
try
90+
{
91+
return operation();
92+
}
93+
catch (Exception ex)
94+
{
95+
attempt++;
96+
if (attempt >= maxRetries)
97+
{
98+
ServerSetup.ConnectionLogger($"Max retries reached. Operation failed: {ex.Message}", LogLevel.Warning);
99+
SentrySdk.CaptureException(ex);
100+
return default(T);
101+
}
102+
103+
// Wait before retrying
104+
Thread.Sleep(1000); // Retry delay
105+
}
106+
}
107+
108+
return default(T);
109+
}
110+
111+
private static void LogBadActor(string remoteIp, string reason)
112+
{
113+
ServerSetup.ConnectionLogger($"Blocking {remoteIp} - Reason: {reason}", LogLevel.Warning);
114+
SentrySdk.CaptureMessage($"{remoteIp} blocked due to {reason}");
115+
ReportEndpoint(remoteIp, $"Blocked due to {reason}");
116+
}
117+
118+
private static bool IsMaliciousUsageType(string usageType)
119+
{
120+
return usageType switch
121+
{
122+
"Commercial" or "Organization" or "Government" or "Military" or "Content Delivery Network" or "Data Center/Web Hosting/Transit" => true,
123+
_ => false
124+
};
125+
}
126+
127+
public static void ReportEndpoint(string remoteIp, string comment)
128+
{
129+
var keyCode = ServerSetup.Instance.KeyCode;
130+
if (keyCode is null || keyCode.Length == 0)
131+
{
132+
ServerSetup.ConnectionLogger("Keycode not valid or not set within ServerConfig.json");
133+
return;
134+
}
135+
136+
try
137+
{
138+
var request = new RestRequest("", Method.Post);
139+
request.AddHeader("Key", keyCode);
140+
request.AddHeader("Accept", "application/json");
141+
request.AddParameter("ip", remoteIp);
142+
request.AddParameter("categories", "14, 15, 16, 21");
143+
request.AddParameter("comment", comment);
144+
var response = ExecuteWithRetry(() => ServerSetup.Instance.RestReport.Execute(request));
145+
146+
if (response?.IsSuccessful == true)
147+
{
148+
return;
149+
}
150+
151+
ServerSetup.ConnectionLogger($"Error reporting {remoteIp} : {comment}");
152+
SentrySdk.CaptureMessage($"Error reporting {remoteIp} : {comment}");
153+
}
154+
catch (Exception ex)
155+
{
156+
ServerSetup.ConnectionLogger($"Exception while reporting {remoteIp}: {ex.Message}", LogLevel.Warning);
157+
SentrySdk.CaptureException(ex);
158+
}
159+
}
160+
}

Zolian.Server.Base/Network/Server/LobbyServer.cs

Lines changed: 12 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,13 @@
44
using Chaos.Packets;
55
using Chaos.Packets.Abstractions;
66
using Darkages.Meta;
7-
using Darkages.Models;
87
using Darkages.Network.Client;
98
using Microsoft.Extensions.Logging;
10-
using RestSharp;
11-
129
using System.Net;
1310
using System.Net.Sockets;
1411
using System.Text;
1512
using Chaos.Networking.Abstractions.Definitions;
1613
using JetBrains.Annotations;
17-
using Newtonsoft.Json;
1814
using ServiceStack;
1915
using ConnectionInfo = Chaos.Networking.Options.ConnectionInfo;
2016
using ServerOptions = Chaos.Networking.Options.ServerOptions;
@@ -34,7 +30,6 @@ public sealed class LobbyServer : ServerBase<ILobbyClient>, ILobbyServer<ILobbyC
3430
{
3531
private readonly IClientFactory<LobbyClient> _clientProvider;
3632
private readonly MServerTable _serverTable;
37-
private const string InternalIP = "192.168.50.1"; // Cannot use ServerConfig due to value needing to be constant
3833

3934
public LobbyServer(
4035
IClientFactory<LobbyClient> clientProvider,
@@ -154,12 +149,21 @@ protected override void OnConnected(Socket clientSocket)
154149
client.OnDisconnected += OnDisconnect;
155150
var safe = false;
156151

157-
foreach (var _ in ServerSetup.Instance.GlobalKnownGoodActorsCache.Values.Where(savedIp => savedIp == ipAddress.ToString()))
152+
var banned = BannedIpCheck(ipAddress.ToString());
153+
if (banned)
154+
{
155+
client.Disconnect();
156+
ServerSetup.ConnectionLogger($"Banned connection attempt from {ip}");
157+
return;
158+
}
159+
160+
var foundIp = ServerSetup.Instance.GlobalKnownGoodActorsCache.Values.First(savedIp => savedIp == ipAddress.ToString());
161+
if (!foundIp.IsEmpty())
158162
safe = true;
159163

160164
if (!safe)
161165
{
162-
var badActor = ClientOnBlackList(ipAddress.ToString());
166+
var badActor = BadActor.ClientOnBlackList(ipAddress.ToString());
163167
if (badActor)
164168
{
165169
try
@@ -202,152 +206,7 @@ private void OnDisconnect(object sender, EventArgs e)
202206
var client = (ILobbyClient)sender!;
203207
ClientRegistry.TryRemove(client.Id, out _);
204208
}
205-
206-
/// <summary>
207-
/// Client IP Check - Blacklist and BOGON list checks
208-
/// </summary>
209-
/// <returns>Boolean, whether or not the IP has been listed as valid</returns>
210-
private bool ClientOnBlackList(string remoteIp)
211-
{
212-
if (remoteIp.IsNullOrEmpty()) return true;
213-
214-
switch (remoteIp)
215-
{
216-
case "127.0.0.1":
217-
case InternalIP:
218-
return false;
219-
}
220-
221-
var bogonCheck = BannedIpCheck(remoteIp);
222-
if (bogonCheck)
223-
{
224-
ServerSetup.ConnectionLogger("-----------------------------------");
225-
ServerSetup.ConnectionLogger($"{remoteIp} is banned and unable to connect");
226-
SentrySdk.CaptureMessage($"{remoteIp} is banned and unable to connect");
227-
return true;
228-
}
229-
230-
try
231-
{
232-
var keyCode = ServerSetup.Instance.KeyCode;
233-
if (keyCode is null || keyCode.Length == 0)
234-
{
235-
ServerSetup.ConnectionLogger("Keycode not valid or not set within ServerConfig.json");
236-
return false;
237-
}
238-
239-
// BLACKLIST check
240-
var request = new RestRequest("", Method.Get);
241-
request.AddHeader("Key", keyCode);
242-
request.AddHeader("Accept", "application/json");
243-
request.AddParameter("ipAddress", remoteIp);
244-
request.AddParameter("maxAgeInDays", "90");
245-
request.AddParameter("verbose", "");
246-
var response = ServerSetup.Instance.RestClient.Execute<Ipdb>(request);
247-
248-
if (response.IsSuccessful)
249-
{
250-
var json = response.Content;
251-
252-
if (json is null || json.Length == 0)
253-
{
254-
ServerSetup.ConnectionLogger($"{remoteIp} - API Issue, response is null or length is 0");
255-
return false;
256-
}
257-
258-
var ipdb = JsonConvert.DeserializeObject<Ipdb>(json!);
259-
var abuseConfidenceScore = ipdb?.Data?.AbuseConfidenceScore;
260-
var tor = ipdb?.Data?.IsTor;
261-
var usageType = ipdb?.Data?.UsageType;
262-
263-
if (tor == true)
264-
{
265-
ServerSetup.ConnectionLogger("---------Lobby-Server---------");
266-
ServerSetup.ConnectionLogger($"{remoteIp} is using tor and automatically blocked", LogLevel.Warning);
267-
SentrySdk.CaptureMessage($"{remoteIp} has a confidence score of {abuseConfidenceScore}, and was using tor, and IP type: {usageType}");
268-
return true;
269-
}
270-
271-
if (usageType == "Reserved")
272-
{
273-
ServerSetup.ConnectionLogger("---------Lobby-Server---------");
274-
ServerSetup.ConnectionLogger($"{remoteIp} was blocked due to being a reserved address (bogon)", LogLevel.Warning);
275-
SentrySdk.CaptureMessage($"{remoteIp} has a confidence score of {abuseConfidenceScore}, and was using a Reserved Address");
276-
return true;
277-
}
278-
279-
if (usageType == "Data Center/Web Hosting/Transit")
280-
{
281-
ServerSetup.ConnectionLogger("---------Lobby-Server---------");
282-
ServerSetup.ConnectionLogger($"{remoteIp} was blocked due to being a data center, web hosting, or transit address", LogLevel.Warning);
283-
SentrySdk.CaptureMessage($"{remoteIp} has a confidence score of {abuseConfidenceScore}, and is a data center, web host, or transit service.");
284-
return true;
285-
}
286-
287-
switch (abuseConfidenceScore)
288-
{
289-
case >= 5:
290-
ServerSetup.ConnectionLogger("---------Lobby-Server---------");
291-
var comment = $"{remoteIp} has been blocked due to a high risk assessment score of {abuseConfidenceScore}, indicating a recognized malicious entity.";
292-
ServerSetup.ConnectionLogger(comment, LogLevel.Warning);
293-
SentrySdk.CaptureMessage($"{remoteIp} has a confidence score of {abuseConfidenceScore}, is using tor: {tor}, and IP type: {usageType}");
294-
ReportEndpoint(remoteIp, comment);
295-
return true;
296-
case >= 0:
297-
return false;
298-
case null:
299-
// Can be null if there is an error in the API, don't want to punish players if its the APIs fault
300-
ServerSetup.ConnectionLogger($"{remoteIp} - API Issue, confidence score was null");
301-
return false;
302-
}
303-
}
304-
else
305-
{
306-
// Can be null if there is an error in the API, don't want to punish players if its the APIs fault
307-
ServerSetup.ConnectionLogger($"{remoteIp} - API Issue, response was not successful");
308-
return false;
309-
}
310-
}
311-
catch (Exception ex)
312-
{
313-
ServerSetup.ConnectionLogger("Unknown issue with IPDB, connections refused", LogLevel.Warning);
314-
ServerSetup.ConnectionLogger($"{ex}");
315-
SentrySdk.CaptureException(ex);
316-
return false;
317-
}
318-
319-
return true;
320-
}
321-
322-
private static void ReportEndpoint(string remoteIp, string comment)
323-
{
324-
var keyCode = ServerSetup.Instance.KeyCode;
325-
if (keyCode is null || keyCode.Length == 0)
326-
{
327-
ServerSetup.ConnectionLogger("Keycode not valid or not set within ServerConfig.json");
328-
return;
329-
}
330-
331-
try
332-
{
333-
var request = new RestRequest("", Method.Post);
334-
request.AddHeader("Key", keyCode);
335-
request.AddHeader("Accept", "application/json");
336-
request.AddParameter("ip", remoteIp);
337-
request.AddParameter("categories", "14, 15, 16, 21");
338-
request.AddParameter("comment", comment);
339-
var response = ServerSetup.Instance.RestReport.Execute(request);
340-
341-
if (response.IsSuccessful) return;
342-
ServerSetup.ConnectionLogger($"Error reporting {remoteIp} : {comment}");
343-
SentrySdk.CaptureMessage($"Error reporting {remoteIp} : {comment}");
344-
}
345-
catch
346-
{
347-
// ignore
348-
}
349-
}
350-
209+
351210
private readonly HashSet<string> _bannedIPs = [];
352211

353212
private bool BannedIpCheck(string ip)

0 commit comments

Comments
 (0)