Skip to content
This repository was archived by the owner on Dec 23, 2022. It is now read-only.

Commit 64a4520

Browse files
committed
Added ban list
Added setting MaxConnections/MaxConnectionsPerIp/BanMinutes
1 parent 488fc39 commit 64a4520

File tree

6 files changed

+192
-37
lines changed

6 files changed

+192
-37
lines changed

RCONServerLib.Tests/ConnectionTests.cs

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,77 +10,80 @@ public void TestAuthFail()
1010
{
1111
var server = new RemoteConServer(IPAddress.Any, 27015);
1212
server.StartListening();
13-
13+
1414
var client = new RemoteConClient();
1515
client.OnAuthResult += Assert.False;
16-
16+
1717
client.Connect("127.0.0.1", 27015);
1818
client.Authenticate("unitfail");
19-
19+
2020
client.Disconnect();
2121
server.StopListening();
2222
}
23-
23+
2424
[Fact]
2525
public void TestAuthSuccess()
2626
{
2727
var server = new RemoteConServer(IPAddress.Any, 27015);
2828
server.StartListening();
29-
29+
3030
var client = new RemoteConClient();
31-
client.OnAuthResult += Assert.True;
32-
31+
client.OnAuthResult += success =>
32+
{
33+
Assert.True(success);
34+
35+
client.Disconnect();
36+
server.StopListening();
37+
};
38+
3339
client.Connect("127.0.0.1", 27015);
3440
client.Authenticate("changeme");
35-
36-
client.Disconnect();
37-
server.StopListening();
3841
}
39-
42+
4043
[Fact]
4144
public void TestCommandFail()
4245
{
4346
var server = new RemoteConServer(IPAddress.Any, 27015);
4447
server.StartListening();
45-
48+
4649
var client = new RemoteConClient();
4750
client.OnAuthResult += success =>
4851
{
4952
//Assert.True(success);
5053
client.SendCommand("testing", result =>
5154
{
5255
Assert.Contains("invalid command", result);
56+
57+
client.Disconnect();
58+
server.StopListening();
5359
});
5460
};
55-
61+
5662
client.Connect("127.0.0.1", 27015);
5763
client.Authenticate("changeme");
58-
59-
client.Disconnect();
60-
server.StopListening();
6164
}
62-
65+
6366
[Fact]
6467
public void TestCommandSuccess()
6568
{
6669
var server = new RemoteConServer(IPAddress.Any, 27015);
6770
server.StartListening();
68-
71+
6972
var client = new RemoteConClient();
7073
client.OnAuthResult += success =>
7174
{
7275
//Assert.True(success);
7376
client.SendCommand("hello", result =>
7477
{
7578
Assert.Contains("world", result);
79+
80+
client.Disconnect();
81+
server.StopListening();
7682
});
7783
};
78-
84+
7985
client.Connect("127.0.0.1", 27015);
8086
client.Authenticate("changeme");
81-
82-
client.Disconnect();
83-
server.StopListening();
8487
}
8588
}
8689
}

RCONServerLib/RCONServerLib.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
<Compile Include="Utils\BinaryReaderExt.cs" />
4646
<Compile Include="Utils\BinaryWriterExt.cs" />
4747
<Compile Include="Utils\CommandManager.cs" />
48+
<Compile Include="Utils\DateTimeExtensions.cs" />
4849
<Compile Include="Utils\Exceptions.cs" />
4950
<Compile Include="Utils\IpExtension.cs" />
5051
</ItemGroup>

RCONServerLib/RemoteConServer.cs

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ public class RemoteConServer
1111
{
1212
public delegate string CommandEventHandler(string command, IList<string> args);
1313

14+
private readonly List<TcpClient> _clients;
15+
1416
private readonly TcpListener _listener;
1517

1618
public readonly CommandManager CommandManager = new CommandManager();
@@ -26,18 +28,27 @@ public RemoteConServer(IPAddress bindAddress, int port)
2628
"192.*.*.*"
2729
};
2830
MaxPasswordTries = 3;
31+
BanMinutes = 15;
2932
Password = "changeme";
3033
SendAuthImmediately = false;
34+
IpBanList = new Dictionary<string, int>();
35+
MaxConnectionsPerIp = 1;
36+
MaxConnections = 5;
3137

3238
#if DEBUG
3339
Debug = true;
3440
#else
3541
Debug = false;
3642
#endif
3743

44+
_clients = new List<TcpClient>();
3845
_listener = new TcpListener(bindAddress, port);
3946
}
4047

48+
/// <summary>
49+
/// </summary>
50+
public Dictionary<string, int> IpBanList { get; set; }
51+
4152
/// <summary>
4253
/// When true closes the connection if the payload of the packet is empty.
4354
/// Default: True
@@ -73,8 +84,16 @@ public RemoteConServer(IPAddress bindAddress, int port)
7384
/// </summary>
7485
public uint MaxPasswordTries { get; set; }
7586

87+
/// <summary>
88+
/// How many minutes a client should be banned when he reached max password tries
89+
/// (Setting this to zero means no ban)
90+
/// Default: 15
91+
/// </summary>
92+
public int BanMinutes { get; set; }
93+
7694
/// <summary>
7795
/// The password to access RCON
96+
/// Default: changeme
7897
/// </summary>
7998
public string Password { get; set; }
8099

@@ -87,24 +106,44 @@ public RemoteConServer(IPAddress bindAddress, int port)
87106

88107
/// <summary>
89108
/// Wether or not we should invoke <see cref="OnCommandReceived" /> instead of internally parsing the command
109+
/// Default: False
90110
/// </summary>
91111
public bool UseCustomCommandHandler { get; set; }
92112

113+
/// <summary>
114+
/// Should we debug the library
115+
/// Default: False (In Release), True (In Debug)
116+
/// </summary>
117+
public bool Debug { get; set; }
118+
119+
/// <summary>
120+
/// How many clients a single IP can connect to the server
121+
/// (Setting this to zero means unlimited)
122+
/// Default: 1
123+
/// </summary>
124+
public uint MaxConnectionsPerIp { get; set; }
125+
126+
/// <summary>
127+
/// How many clients can connect to the server
128+
/// (Setting this to zero means unlimited)
129+
/// Default: 5
130+
/// </summary>
131+
public uint MaxConnections { get; set; }
132+
93133
/// <summary>
94134
/// Event Handler to parse custom commands
95135
/// </summary>
96136
public event CommandEventHandler OnCommandReceived;
97137

98-
public bool Debug { get; set; }
99-
100138
/// <summary>
101139
/// Starts the TCPListener and begins accepting clients
102140
/// </summary>
103141
public void StartListening()
104142
{
105143
_listener.Start();
106144
_listener.BeginAcceptTcpClient(OnAccept, _listener);
107-
LogDebug("Started listening on " + ((IPEndPoint)_listener.LocalEndpoint).Address + ", Password is: \"" + Password + "\"");
145+
LogDebug("Started listening on " + ((IPEndPoint) _listener.LocalEndpoint).Address + ", Password is: \"" +
146+
Password + "\"");
108147
}
109148

110149
public void StopListening()
@@ -116,17 +155,58 @@ private void OnAccept(IAsyncResult result)
116155
{
117156
var tcpClient = _listener.EndAcceptTcpClient(result);
118157

158+
var ip = ((IPEndPoint) tcpClient.Client.RemoteEndPoint).Address;
159+
160+
if (MaxConnections > 0)
161+
if (_clients.Count >= MaxConnections)
162+
{
163+
LogDebug("Rejected new connection from " + ip + " (Server full.)");
164+
tcpClient.Close();
165+
return;
166+
}
167+
168+
if (MaxConnectionsPerIp > 0)
169+
{
170+
var count = 0;
171+
foreach (var tcpClient1 in _clients)
172+
if (((IPEndPoint) tcpClient1.Client.RemoteEndPoint).Address.ToString() == ip.ToString())
173+
count++;
174+
175+
if (count >= MaxConnectionsPerIp)
176+
{
177+
LogDebug("Rejected new connection from " + ip + " (Too many connections from this IP)");
178+
tcpClient.Close();
179+
return;
180+
}
181+
}
182+
119183
if (EnableIpWhitelist)
120184
if (!IpWhitelist.Any(p =>
121-
IpExtension.Match(p, ((IPEndPoint) tcpClient.Client.RemoteEndPoint).Address.ToString())))
185+
IpExtension.Match(p, ip.ToString())))
186+
{
187+
LogDebug("Rejected new connection from " + ip + " (Not in whitelist)");
188+
tcpClient.Close();
189+
return;
190+
}
191+
192+
if (IpBanList.ContainsKey(ip.ToString()))
193+
{
194+
if (IpBanList[ip.ToString()] - DateTime.Now.ToUnixTimestamp() > 0)
122195
{
196+
LogDebug("Rejected new connection from " + ip + " (Banned till " +
197+
DateTimeExtensions.FromUnixTimestamp(IpBanList[ip.ToString()]).ToString("F") + ")");
123198
tcpClient.Close();
124199
return;
125200
}
126201

127-
LogDebug("Accepted new connection from " + ((IPEndPoint) tcpClient.Client.RemoteEndPoint).Address);
202+
IpBanList.Remove(ip.ToString());
203+
}
204+
205+
LogDebug("Accepted new connection from " + ip);
128206
var client = new RemoteConTcpClient(tcpClient, this);
129207

208+
_clients.Add(tcpClient);
209+
130210
_listener.BeginAcceptTcpClient(OnAccept, _listener);
131211
}
132212

RCONServerLib/RemoteConTcpClient.cs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,9 @@ internal void ParsePacket(byte[] rawPacket)
205205
{
206206
var packet = new RemoteConPacket(rawPacket);
207207

208-
if(!_isUnitTest)
208+
if (!_isUnitTest)
209209
_remoteConServer.LogDebug(((IPEndPoint) _tcp.Client.RemoteEndPoint).Address + " sent packet " +
210-
packet.Type + "!");
210+
packet.Type + "!");
211211

212212
// Do not allow any other packets than auth to be sent when client is not authenticated
213213
if (!Authenticated)
@@ -223,9 +223,9 @@ internal void ParsePacket(byte[] rawPacket)
223223

224224
if (packet.Payload == _remoteConServer.Password)
225225
{
226-
if(!_isUnitTest)
226+
if (!_isUnitTest)
227227
_remoteConServer.LogDebug(((IPEndPoint) _tcp.Client.RemoteEndPoint).Address +
228-
" successfully authenticated!");
228+
" successfully authenticated!");
229229
Authenticated = true;
230230

231231
if (!_remoteConServer.SendAuthImmediately)
@@ -237,13 +237,19 @@ internal void ParsePacket(byte[] rawPacket)
237237

238238
if (_authTries >= _remoteConServer.MaxPasswordTries)
239239
{
240+
if (_remoteConServer.BanMinutes > 0)
241+
{
242+
_remoteConServer.IpBanList.Add(((IPEndPoint) _tcp.Client.RemoteEndPoint).Address.ToString(),
243+
DateTime.Now.AddMinutes(_remoteConServer.BanMinutes).ToUnixTimestamp());
244+
}
245+
240246
CloseConnection();
241247
return;
242248
}
243249

244-
if(!_isUnitTest)
250+
if (!_isUnitTest)
245251
_remoteConServer.LogDebug(((IPEndPoint) _tcp.Client.RemoteEndPoint).Address +
246-
" entered wrong password!");
252+
" entered wrong password!");
247253

248254
if (!_remoteConServer.SendAuthImmediately)
249255
SendPacket(new RemoteConPacket(packet.Id, RemoteConPacket.PacketType.ResponseValue, ""));
@@ -258,7 +264,7 @@ internal void ParsePacket(byte[] rawPacket)
258264
{
259265
if (_isUnitTest)
260266
throw new InvalidPacketTypeException();
261-
267+
262268
_remoteConServer.LogDebug(((IPEndPoint) _tcp.Client.RemoteEndPoint).Address +
263269
" sent a packet with invalid type!");
264270

@@ -313,8 +319,8 @@ internal void ParsePacket(byte[] rawPacket)
313319
{
314320
if (_isUnitTest)
315321
throw;
316-
317-
if(!_isUnitTest)
322+
323+
if (!_isUnitTest)
318324
_remoteConServer.LogDebug(string.Format("Client {0} caused an exception: {1} and was killed.",
319325
((IPEndPoint) _tcp.Client.RemoteEndPoint).Address, e.Message));
320326

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System;
2+
3+
namespace RCONServerLib.Utils
4+
{
5+
public static class DateTimeExtensions
6+
{
7+
/// <summary>
8+
/// Converts a given DateTime into a Unix timestamp
9+
/// </summary>
10+
/// <param name="value">Any DateTime</param>
11+
/// <returns>The given DateTime in Unix timestamp format</returns>
12+
public static int ToUnixTimestamp(this DateTime value)
13+
{
14+
return (int) Math.Truncate(value.ToUniversalTime().Subtract(new DateTime(1970, 1, 1)).TotalSeconds);
15+
}
16+
17+
/// <summary>
18+
/// Gets a Unix timestamp representing the current moment
19+
/// </summary>
20+
/// <param name="ignored">Parameter ignored</param>
21+
/// <returns>Now expressed as a Unix timestamp</returns>
22+
public static int UnixTimestamp(this DateTime ignored)
23+
{
24+
return (int) Math.Truncate(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds);
25+
}
26+
27+
public static DateTime FromUnixTimestamp(int unixTimeStamp)
28+
{
29+
var dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
30+
dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToLocalTime();
31+
return dtDateTime;
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)