aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Framework/BasicDOSProtector.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Framework/BasicDOSProtector.cs')
-rw-r--r--OpenSim/Framework/BasicDOSProtector.cs275
1 files changed, 275 insertions, 0 deletions
diff --git a/OpenSim/Framework/BasicDOSProtector.cs b/OpenSim/Framework/BasicDOSProtector.cs
new file mode 100644
index 0000000..89bfa94
--- /dev/null
+++ b/OpenSim/Framework/BasicDOSProtector.cs
@@ -0,0 +1,275 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27using System;
28using System.Collections.Generic;
29using System.Reflection;
30using log4net;
31
32namespace OpenSim.Framework
33{
34
35 public class BasicDOSProtector
36 {
37 public enum ThrottleAction
38 {
39 DoThrottledMethod,
40 DoThrow
41 }
42 private readonly CircularBuffer<int> _generalRequestTimes; // General request checker
43 private readonly BasicDosProtectorOptions _options;
44 private readonly Dictionary<string, CircularBuffer<int>> _deeperInspection; // per client request checker
45 private readonly Dictionary<string, int> _tempBlocked; // blocked list
46 private readonly Dictionary<string, int> _sessions;
47 private readonly System.Timers.Timer _forgetTimer; // Cleanup timer
48 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
49 private readonly System.Threading.ReaderWriterLockSlim _blockLockSlim = new System.Threading.ReaderWriterLockSlim();
50 private readonly System.Threading.ReaderWriterLockSlim _sessionLockSlim = new System.Threading.ReaderWriterLockSlim();
51 public BasicDOSProtector(BasicDosProtectorOptions options)
52 {
53 _generalRequestTimes = new CircularBuffer<int>(options.MaxRequestsInTimeframe + 1, true);
54 _generalRequestTimes.Put(0);
55 _options = options;
56 _deeperInspection = new Dictionary<string, CircularBuffer<int>>();
57 _tempBlocked = new Dictionary<string, int>();
58 _sessions = new Dictionary<string, int>();
59 _forgetTimer = new System.Timers.Timer();
60 _forgetTimer.Elapsed += delegate
61 {
62 _forgetTimer.Enabled = false;
63
64 List<string> removes = new List<string>();
65 _blockLockSlim.EnterReadLock();
66 foreach (string str in _tempBlocked.Keys)
67 {
68 if (
69 Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(),
70 _tempBlocked[str]) > 0)
71 removes.Add(str);
72 }
73 _blockLockSlim.ExitReadLock();
74 lock (_deeperInspection)
75 {
76 _blockLockSlim.EnterWriteLock();
77 for (int i = 0; i < removes.Count; i++)
78 {
79 _tempBlocked.Remove(removes[i]);
80 _deeperInspection.Remove(removes[i]);
81 _sessions.Remove(removes[i]);
82 }
83 _blockLockSlim.ExitWriteLock();
84 }
85 foreach (string str in removes)
86 {
87 m_log.InfoFormat("[{0}] client: {1} is no longer blocked.",
88 _options.ReportingName, str);
89 }
90 _blockLockSlim.EnterReadLock();
91 if (_tempBlocked.Count > 0)
92 _forgetTimer.Enabled = true;
93 _blockLockSlim.ExitReadLock();
94 };
95
96 _forgetTimer.Interval = _options.ForgetTimeSpan.TotalMilliseconds;
97 }
98
99 /// <summary>
100 /// Given a string Key, Returns if that context is blocked
101 /// </summary>
102 /// <param name="key">A Key identifying the context</param>
103 /// <returns>bool Yes or No, True or False for blocked</returns>
104 public bool IsBlocked(string key)
105 {
106 bool ret = false;
107 _blockLockSlim.EnterReadLock();
108 ret = _tempBlocked.ContainsKey(key);
109 _blockLockSlim.ExitReadLock();
110 return ret;
111 }
112
113 /// <summary>
114 /// Process the velocity of this context
115 /// </summary>
116 /// <param name="key"></param>
117 /// <param name="endpoint"></param>
118 /// <returns></returns>
119 public bool Process(string key, string endpoint)
120 {
121 if (_options.MaxRequestsInTimeframe < 1 || _options.RequestTimeSpan.TotalMilliseconds < 1)
122 return true;
123
124 string clientstring = key;
125
126 _blockLockSlim.EnterReadLock();
127 if (_tempBlocked.ContainsKey(clientstring))
128 {
129 _blockLockSlim.ExitReadLock();
130
131 if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod)
132 return false;
133 else
134 throw new System.Security.SecurityException("Throttled");
135 }
136
137 _blockLockSlim.ExitReadLock();
138
139 lock (_generalRequestTimes)
140 _generalRequestTimes.Put(Util.EnvironmentTickCount());
141
142 if (_options.MaxConcurrentSessions > 0)
143 {
144 int sessionscount = 0;
145
146 _sessionLockSlim.EnterReadLock();
147 if (_sessions.ContainsKey(key))
148 sessionscount = _sessions[key];
149 _sessionLockSlim.ExitReadLock();
150
151 if (sessionscount > _options.MaxConcurrentSessions)
152 {
153 // Add to blocking and cleanup methods
154 lock (_deeperInspection)
155 {
156 _blockLockSlim.EnterWriteLock();
157 if (!_tempBlocked.ContainsKey(clientstring))
158 {
159 _tempBlocked.Add(clientstring,
160 Util.EnvironmentTickCount() +
161 (int) _options.ForgetTimeSpan.TotalMilliseconds);
162 _forgetTimer.Enabled = true;
163 m_log.WarnFormat("[{0}]: client: {1} is blocked for {2} milliseconds based on concurrency, X-ForwardedForAllowed status is {3}, endpoint:{4}", _options.ReportingName, clientstring, _options.ForgetTimeSpan.TotalMilliseconds, _options.AllowXForwardedFor, endpoint);
164
165 }
166 else
167 _tempBlocked[clientstring] = Util.EnvironmentTickCount() +
168 (int) _options.ForgetTimeSpan.TotalMilliseconds;
169 _blockLockSlim.ExitWriteLock();
170
171 }
172
173
174 }
175 else
176 ProcessConcurrency(key, endpoint);
177 }
178 if (_generalRequestTimes.Size == _generalRequestTimes.Capacity &&
179 (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _generalRequestTimes.Get()) <
180 _options.RequestTimeSpan.TotalMilliseconds))
181 {
182 //Trigger deeper inspection
183 if (DeeperInspection(key, endpoint))
184 return true;
185 if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod)
186 return false;
187 else
188 throw new System.Security.SecurityException("Throttled");
189 }
190 return true;
191 }
192 private void ProcessConcurrency(string key, string endpoint)
193 {
194 _sessionLockSlim.EnterWriteLock();
195 if (_sessions.ContainsKey(key))
196 _sessions[key] = _sessions[key] + 1;
197 else
198 _sessions.Add(key,1);
199 _sessionLockSlim.ExitWriteLock();
200 }
201 public void ProcessEnd(string key, string endpoint)
202 {
203 _sessionLockSlim.EnterWriteLock();
204 if (_sessions.ContainsKey(key))
205 {
206 _sessions[key]--;
207 if (_sessions[key] <= 0)
208 _sessions.Remove(key);
209 }
210 else
211 _sessions.Add(key, 1);
212
213 _sessionLockSlim.ExitWriteLock();
214 }
215
216 /// <summary>
217 /// At this point, the rate limiting code needs to track 'per user' velocity.
218 /// </summary>
219 /// <param name="key">Context Key, string representing a rate limiting context</param>
220 /// <param name="endpoint"></param>
221 /// <returns></returns>
222 private bool DeeperInspection(string key, string endpoint)
223 {
224 lock (_deeperInspection)
225 {
226 string clientstring = key;
227
228
229 if (_deeperInspection.ContainsKey(clientstring))
230 {
231 _deeperInspection[clientstring].Put(Util.EnvironmentTickCount());
232 if (_deeperInspection[clientstring].Size == _deeperInspection[clientstring].Capacity &&
233 (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _deeperInspection[clientstring].Get()) <
234 _options.RequestTimeSpan.TotalMilliseconds))
235 {
236 //Looks like we're over the limit
237 _blockLockSlim.EnterWriteLock();
238 if (!_tempBlocked.ContainsKey(clientstring))
239 _tempBlocked.Add(clientstring, Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds);
240 else
241 _tempBlocked[clientstring] = Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds;
242 _blockLockSlim.ExitWriteLock();
243
244 m_log.WarnFormat("[{0}]: client: {1} is blocked for {2} milliseconds, X-ForwardedForAllowed status is {3}, endpoint:{4}", _options.ReportingName, clientstring, _options.ForgetTimeSpan.TotalMilliseconds, _options.AllowXForwardedFor, endpoint);
245
246 return false;
247 }
248 //else
249 // return true;
250 }
251 else
252 {
253 _deeperInspection.Add(clientstring, new CircularBuffer<int>(_options.MaxRequestsInTimeframe + 1, true));
254 _deeperInspection[clientstring].Put(Util.EnvironmentTickCount());
255 _forgetTimer.Enabled = true;
256 }
257
258 }
259 return true;
260 }
261
262 }
263
264
265 public class BasicDosProtectorOptions
266 {
267 public int MaxRequestsInTimeframe;
268 public TimeSpan RequestTimeSpan;
269 public TimeSpan ForgetTimeSpan;
270 public bool AllowXForwardedFor;
271 public string ReportingName = "BASICDOSPROTECTOR";
272 public BasicDOSProtector.ThrottleAction ThrottledAction = BasicDOSProtector.ThrottleAction.DoThrottledMethod;
273 public int MaxConcurrentSessions;
274 }
275}