1- using System . Net ;
1+ using System . Collections . Concurrent ;
2+ using System . Net ;
23
34using Darkages . Models ;
45
1213
1314namespace Darkages . Network . Server ;
1415
16+ public class ReportInfo
17+ {
18+ public string RemoteIp { get ; init ; }
19+ public string Comment { get ; init ; }
20+ public DateTime FirstAttemptTime { get ; init ; }
21+ }
22+
1523public static class BadActor
1624{
25+ private static readonly ConcurrentQueue < ReportInfo > RetryQueue = [ ] ;
26+ private static bool _isProcessingQueue ;
1727 private const string InternalIP = "192.168.50.1" ; // Cannot use ServerConfig due to value needing to be constant
1828
29+ public static void StartProcessingQueue ( )
30+ {
31+ if ( _isProcessingQueue ) return ;
32+
33+ _isProcessingQueue = true ;
34+ Task . Run ( ProcessRetryQueue ) ;
35+ }
36+
1937 public static bool ClientOnBlackList ( string remoteIp )
2038 {
2139 if ( remoteIp . IsNullOrEmpty ( ) || ! IPAddress . TryParse ( remoteIp , out _ ) ) return true ;
@@ -46,16 +64,17 @@ public static bool ClientOnBlackList(string remoteIp)
4664 var tor = ipdb ? . Data ? . IsTor ;
4765 var usageType = ipdb ? . Data ? . UsageType ;
4866
67+ // Block if using tor, potentially malicious
4968 if ( tor == true )
5069 {
51- LogBadActor ( remoteIp , "using tor" ) ;
70+ LogBadActor ( remoteIp , "using tor, potentially malicious " ) ;
5271 return true ;
5372 }
5473
55- // Block if known malicious usage type
56- if ( IsMaliciousUsageType ( usageType ) )
74+ // Block if an unauthorized usage type
75+ if ( IsBlockedUsageType ( usageType ) )
5776 {
58- LogBadActor ( remoteIp , $ "using { usageType } ") ;
77+ LogBlockedType ( remoteIp , $ "using { usageType } ") ;
5978 return true ;
6079 }
6180
@@ -112,10 +131,16 @@ private static void LogBadActor(string remoteIp, string reason)
112131 {
113132 ServerSetup . ConnectionLogger ( $ "Blocking { remoteIp } - Reason: { reason } ", LogLevel . Warning ) ;
114133 SentrySdk . CaptureMessage ( $ "{ remoteIp } blocked due to { reason } ") ;
115- ReportEndpoint ( remoteIp , $ "Blocked due to { reason } ") ;
134+ ReportMaliciousEndpoint ( remoteIp , $ "Blocked due to { reason } ") ;
116135 }
117136
118- private static bool IsMaliciousUsageType ( string usageType )
137+ private static void LogBlockedType ( string remoteIp , string reason )
138+ {
139+ ServerSetup . ConnectionLogger ( $ "Blocking { remoteIp } - Using: { reason } ", LogLevel . Warning ) ;
140+ ReportSuspiciousEndpoint ( remoteIp , "Blocked due to Web Spam, Port Scanning" ) ;
141+ }
142+
143+ private static bool IsBlockedUsageType ( string usageType )
119144 {
120145 return usageType switch
121146 {
@@ -124,7 +149,7 @@ private static bool IsMaliciousUsageType(string usageType)
124149 } ;
125150 }
126151
127- public static void ReportEndpoint ( string remoteIp , string comment )
152+ public static void ReportMaliciousEndpoint ( string remoteIp , string comment )
128153 {
129154 var keyCode = ServerSetup . Instance . KeyCode ;
130155 if ( keyCode is null || keyCode . Length == 0 )
@@ -147,10 +172,127 @@ public static void ReportEndpoint(string remoteIp, string comment)
147172 ServerSetup . ConnectionLogger ( $ "Error reporting { remoteIp } : { comment } ") ;
148173 SentrySdk . CaptureMessage ( $ "Error reporting { remoteIp } : { comment } ") ;
149174 }
175+ catch ( HttpRequestException httpEx ) when ( httpEx . Message . Contains ( "TooManyRequests" ) )
176+ {
177+ ServerSetup . ConnectionLogger ( $ "Exception while reporting { remoteIp } : Too many requests.", LogLevel . Warning ) ;
178+ // add to queue where we report them also for DDoS (4)
179+ AddToRetryQueue ( remoteIp , comment ) ;
180+ }
150181 catch ( Exception ex )
151182 {
152183 ServerSetup . ConnectionLogger ( $ "Exception while reporting { remoteIp } : { ex . Message } ", LogLevel . Warning ) ;
153184 SentrySdk . CaptureException ( ex ) ;
154185 }
155186 }
187+
188+ private static void ReportSuspiciousEndpoint ( string remoteIp , string comment )
189+ {
190+ var keyCode = ServerSetup . Instance . KeyCode ;
191+ if ( keyCode is null || keyCode . Length == 0 )
192+ {
193+ ServerSetup . ConnectionLogger ( "Keycode not valid or not set within ServerConfig.json" ) ;
194+ return ;
195+ }
196+
197+ try
198+ {
199+ var request = new RestRequest ( "" , Method . Post ) ;
200+ request . AddHeader ( "Key" , keyCode ) ;
201+ request . AddHeader ( "Accept" , "application/json" ) ;
202+ request . AddParameter ( "ip" , remoteIp ) ;
203+ request . AddParameter ( "categories" , "10, 14" ) ;
204+ request . AddParameter ( "comment" , comment ) ;
205+ var response = ExecuteWithRetry ( ( ) => ServerSetup . Instance . RestReport . Execute ( request ) ) ;
206+
207+ if ( response . Result ? . IsSuccessful == true ) return ;
208+ ServerSetup . ConnectionLogger ( $ "Error reporting { remoteIp } : { comment } ") ;
209+ SentrySdk . CaptureMessage ( $ "Error reporting { remoteIp } : { comment } ") ;
210+ }
211+ catch ( HttpRequestException httpEx ) when ( httpEx . Message . Contains ( "TooManyRequests" ) )
212+ {
213+ ServerSetup . ConnectionLogger ( $ "Exception while reporting { remoteIp } : Too many requests.", LogLevel . Warning ) ;
214+ // add to queue where we report them also for DDoS (4)
215+ AddToRetryQueue ( remoteIp , comment ) ;
216+ }
217+ catch ( Exception ex )
218+ {
219+ ServerSetup . ConnectionLogger ( $ "Exception while reporting { remoteIp } : { ex . Message } ", LogLevel . Warning ) ;
220+ SentrySdk . CaptureException ( ex ) ;
221+ }
222+ }
223+
224+ private static void AddToRetryQueue ( string remoteIp , string comment )
225+ {
226+ RetryQueue . Enqueue ( new ReportInfo
227+ {
228+ RemoteIp = remoteIp ,
229+ Comment = comment ,
230+ FirstAttemptTime = DateTime . Now
231+ } ) ;
232+ }
233+
234+ private static async Task ProcessRetryQueue ( )
235+ {
236+ while ( _isProcessingQueue )
237+ {
238+ if ( ! RetryQueue . IsEmpty )
239+ {
240+ // Process each report in the queue
241+ if ( RetryQueue . TryDequeue ( out var report ) )
242+ {
243+ var elapsedTime = DateTime . Now - report . FirstAttemptTime ;
244+
245+ if ( elapsedTime . TotalMinutes >= 1 )
246+ {
247+ var success = await ReportSuspiciousEndpointWithDDoS ( report ) ;
248+ if ( success )
249+ {
250+ ServerSetup . ConnectionLogger ( $ "Successfully reported { report . RemoteIp } after retry.") ;
251+ }
252+ else
253+ {
254+ RetryQueue . Enqueue ( report ) ;
255+ ServerSetup . ConnectionLogger ( $ "Retry failed for { report . RemoteIp } , re-queued.") ;
256+ }
257+ }
258+ else
259+ {
260+ await Task . Delay ( TimeSpan . FromMinutes ( 1 ) - elapsedTime ) ;
261+ RetryQueue . Enqueue ( report ) ;
262+ }
263+ }
264+ }
265+
266+ await Task . Delay ( 5000 ) ;
267+ }
268+ }
269+
270+ private static async Task < bool > ReportSuspiciousEndpointWithDDoS ( ReportInfo report )
271+ {
272+ var adjustedComment = $ "{ report . Comment } - DDoS";
273+
274+ try
275+ {
276+ var keyCode = ServerSetup . Instance . KeyCode ;
277+ if ( string . IsNullOrEmpty ( keyCode ) )
278+ {
279+ ServerSetup . ConnectionLogger ( "Keycode not valid or not set within ServerConfig.json" ) ;
280+ return false ;
281+ }
282+
283+ var request = new RestRequest ( "" , Method . Post ) ;
284+ request . AddHeader ( "Key" , keyCode ) ;
285+ request . AddHeader ( "Accept" , "application/json" ) ;
286+ request . AddParameter ( "ip" , report . RemoteIp ) ;
287+ request . AddParameter ( "categories" , "4, 10, 14" ) ;
288+ request . AddParameter ( "comment" , adjustedComment ) ;
289+
290+ var response = await ServerSetup . Instance . RestReport . ExecuteAsync ( request ) ;
291+ return response . IsSuccessful ;
292+ }
293+ catch
294+ {
295+ return false ;
296+ }
297+ }
156298}
0 commit comments