OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
BasicDOSProtector.cs
Go to the documentation of this file.
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  */
27 using System;
28 using System.Collections.Generic;
29 using System.Reflection;
30 using log4net;
31 
32 namespace 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();
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 
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 
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 
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 }
bool Process(string key, string endpoint)
Process the velocity of this context
void ProcessEnd(string key, string endpoint)
OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString key
Definition: ICM_Api.cs:31
bool IsBlocked(string key)
Given a string Key, Returns if that context is blocked
BasicDOSProtector(BasicDosProtectorOptions options)