aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Framework
diff options
context:
space:
mode:
authorteravus2013-10-07 21:35:55 -0500
committerteravus2013-10-07 21:35:55 -0500
commitf76cc6036ebf446553ee5201321879538dafe3b2 (patch)
tree7e33eee605c3baf04a16422f06ac3986f0f27eaa /OpenSim/Framework
parent* Added a unique and interesting WebSocket grid login processor by hijacking ... (diff)
downloadopensim-SC-f76cc6036ebf446553ee5201321879538dafe3b2.zip
opensim-SC-f76cc6036ebf446553ee5201321879538dafe3b2.tar.gz
opensim-SC-f76cc6036ebf446553ee5201321879538dafe3b2.tar.bz2
opensim-SC-f76cc6036ebf446553ee5201321879538dafe3b2.tar.xz
* Added a Basic DOS protection container/base object for the most common HTTP Server handlers. XMLRPC Handler, GenericHttpHandler and <Various>StreamHandler
* Applied the XmlRpcBasicDOSProtector.cs to the login service as both an example, and good practice. * Applied the BaseStreamHandlerBasicDOSProtector.cs to the friends service as an example of the DOS Protector on StreamHandlers * Added CircularBuffer, used for CPU and Memory friendly rate monitoring. * DosProtector has 2 states, 1. Just Check for blocked users and check general velocity, 2. Track velocity per user, It only jumps to 2 if it's getting a lot of requests, and state 1 is about as resource friendly as if it wasn't even there.
Diffstat (limited to 'OpenSim/Framework')
-rw-r--r--OpenSim/Framework/CircularBuffer.cs312
-rw-r--r--OpenSim/Framework/Servers/HttpServer/BaseStreamHandlerBasicDOSProtector.cs233
-rw-r--r--OpenSim/Framework/Servers/HttpServer/GenericHTTPBasicDOSProtector.cs238
-rw-r--r--OpenSim/Framework/Servers/HttpServer/XmlRpcBasicDOSProtector.cs211
4 files changed, 994 insertions, 0 deletions
diff --git a/OpenSim/Framework/CircularBuffer.cs b/OpenSim/Framework/CircularBuffer.cs
new file mode 100644
index 0000000..e919337
--- /dev/null
+++ b/OpenSim/Framework/CircularBuffer.cs
@@ -0,0 +1,312 @@
1/*
2Copyright (c) 2012, Alex Regueiro
3All rights reserved.
4Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
5following conditions are met:
6
7Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
9in the documentation and/or other materials provided with the distribution.
10
11THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
12BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
13IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
14OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
15OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
16OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
17POSSIBILITY OF SUCH DAMAGE.
18*/
19using System;
20using System.Collections;
21using System.Collections.Generic;
22using System.Threading;
23
24namespace OpenSim.Framework
25{
26 public class CircularBuffer<T> : ICollection<T>, IEnumerable<T>, ICollection, IEnumerable
27 {
28 private int capacity;
29 private int size;
30 private int head;
31 private int tail;
32 private T[] buffer;
33
34 [NonSerialized()]
35 private object syncRoot;
36
37 public CircularBuffer(int capacity)
38 : this(capacity, false)
39 {
40 }
41
42 public CircularBuffer(int capacity, bool allowOverflow)
43 {
44 if (capacity < 0)
45 throw new ArgumentException("Needs to have at least 1","capacity");
46
47 this.capacity = capacity;
48 size = 0;
49 head = 0;
50 tail = 0;
51 buffer = new T[capacity];
52 AllowOverflow = allowOverflow;
53 }
54
55 public bool AllowOverflow
56 {
57 get;
58 set;
59 }
60
61 public int Capacity
62 {
63 get { return capacity; }
64 set
65 {
66 if (value == capacity)
67 return;
68
69 if (value < size)
70 throw new ArgumentOutOfRangeException("value","Capacity is too small.");
71
72 var dst = new T[value];
73 if (size > 0)
74 CopyTo(dst);
75 buffer = dst;
76
77 capacity = value;
78 }
79 }
80
81 public int Size
82 {
83 get { return size; }
84 }
85
86 public bool Contains(T item)
87 {
88 int bufferIndex = head;
89 var comparer = EqualityComparer<T>.Default;
90 for (int i = 0; i < size; i++, bufferIndex++)
91 {
92 if (bufferIndex == capacity)
93 bufferIndex = 0;
94
95 if (item == null && buffer[bufferIndex] == null)
96 return true;
97 else if ((buffer[bufferIndex] != null) &&
98 comparer.Equals(buffer[bufferIndex], item))
99 return true;
100 }
101
102 return false;
103 }
104
105 public void Clear()
106 {
107 size = 0;
108 head = 0;
109 tail = 0;
110 }
111
112 public int Put(T[] src)
113 {
114 return Put(src, 0, src.Length);
115 }
116
117 public int Put(T[] src, int offset, int count)
118 {
119 if (!AllowOverflow && count > capacity - size)
120 throw new InvalidOperationException("Buffer Overflow");
121
122 int srcIndex = offset;
123 for (int i = 0; i < count; i++, tail++, srcIndex++)
124 {
125 if (tail == capacity)
126 tail = 0;
127 buffer[tail] = src[srcIndex];
128 }
129 size = Math.Min(size + count, capacity);
130 return count;
131 }
132
133 public void Put(T item)
134 {
135 if (!AllowOverflow && size == capacity)
136 throw new InvalidOperationException("Buffer Overflow");
137
138 buffer[tail] = item;
139 if (++tail == capacity)
140 tail = 0;
141 size++;
142 }
143
144 public void Skip(int count)
145 {
146 head += count;
147 if (head >= capacity)
148 head -= capacity;
149 }
150
151 public T[] Get(int count)
152 {
153 var dst = new T[count];
154 Get(dst);
155 return dst;
156 }
157
158 public int Get(T[] dst)
159 {
160 return Get(dst, 0, dst.Length);
161 }
162
163 public int Get(T[] dst, int offset, int count)
164 {
165 int realCount = Math.Min(count, size);
166 int dstIndex = offset;
167 for (int i = 0; i < realCount; i++, head++, dstIndex++)
168 {
169 if (head == capacity)
170 head = 0;
171 dst[dstIndex] = buffer[head];
172 }
173 size -= realCount;
174 return realCount;
175 }
176
177 public T Get()
178 {
179 if (size == 0)
180 throw new InvalidOperationException("Buffer Empty");
181
182 var item = buffer[head];
183 if (++head == capacity)
184 head = 0;
185 size--;
186 return item;
187 }
188
189 public void CopyTo(T[] array)
190 {
191 CopyTo(array, 0);
192 }
193
194 public void CopyTo(T[] array, int arrayIndex)
195 {
196 CopyTo(0, array, arrayIndex, size);
197 }
198
199 public void CopyTo(int index, T[] array, int arrayIndex, int count)
200 {
201 if (count > size)
202 throw new ArgumentOutOfRangeException("count", "Count Too Large");
203
204 int bufferIndex = head;
205 for (int i = 0; i < count; i++, bufferIndex++, arrayIndex++)
206 {
207 if (bufferIndex == capacity)
208 bufferIndex = 0;
209 array[arrayIndex] = buffer[bufferIndex];
210 }
211 }
212
213 public IEnumerator<T> GetEnumerator()
214 {
215 int bufferIndex = head;
216 for (int i = 0; i < size; i++, bufferIndex++)
217 {
218 if (bufferIndex == capacity)
219 bufferIndex = 0;
220
221 yield return buffer[bufferIndex];
222 }
223 }
224
225 public T[] GetBuffer()
226 {
227 return buffer;
228 }
229
230 public T[] ToArray()
231 {
232 var dst = new T[size];
233 CopyTo(dst);
234 return dst;
235 }
236
237 #region ICollection<T> Members
238
239 int ICollection<T>.Count
240 {
241 get { return Size; }
242 }
243
244 bool ICollection<T>.IsReadOnly
245 {
246 get { return false; }
247 }
248
249 void ICollection<T>.Add(T item)
250 {
251 Put(item);
252 }
253
254 bool ICollection<T>.Remove(T item)
255 {
256 if (size == 0)
257 return false;
258
259 Get();
260 return true;
261 }
262
263 #endregion
264
265 #region IEnumerable<T> Members
266
267 IEnumerator<T> IEnumerable<T>.GetEnumerator()
268 {
269 return GetEnumerator();
270 }
271
272 #endregion
273
274 #region ICollection Members
275
276 int ICollection.Count
277 {
278 get { return Size; }
279 }
280
281 bool ICollection.IsSynchronized
282 {
283 get { return false; }
284 }
285
286 object ICollection.SyncRoot
287 {
288 get
289 {
290 if (syncRoot == null)
291 Interlocked.CompareExchange(ref syncRoot, new object(), null);
292 return syncRoot;
293 }
294 }
295
296 void ICollection.CopyTo(Array array, int arrayIndex)
297 {
298 CopyTo((T[])array, arrayIndex);
299 }
300
301 #endregion
302
303 #region IEnumerable Members
304
305 IEnumerator IEnumerable.GetEnumerator()
306 {
307 return (IEnumerator)GetEnumerator();
308 }
309
310 #endregion
311 }
312}
diff --git a/OpenSim/Framework/Servers/HttpServer/BaseStreamHandlerBasicDOSProtector.cs b/OpenSim/Framework/Servers/HttpServer/BaseStreamHandlerBasicDOSProtector.cs
new file mode 100644
index 0000000..8fc9a8a
--- /dev/null
+++ b/OpenSim/Framework/Servers/HttpServer/BaseStreamHandlerBasicDOSProtector.cs
@@ -0,0 +1,233 @@
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 OpenSim.Framework;
28using System.Collections.Generic;
29using System.IO;
30using System.Reflection;
31using log4net;
32
33namespace OpenSim.Framework.Servers.HttpServer
34{
35 /// <summary>
36 /// BaseStreamHandlerBasicDOSProtector Base streamed request handler.
37 /// </summary>
38 /// <remarks>
39 /// Inheriting classes should override ProcessRequest() rather than Handle()
40 /// </remarks>
41 public abstract class BaseStreamHandlerBasicDOSProtector : BaseRequestHandler, IStreamedRequestHandler
42 {
43 private readonly CircularBuffer<int> _generalRequestTimes;
44 private readonly BasicDosProtectorOptions _options;
45 private readonly Dictionary<string, CircularBuffer<int>> _deeperInspection;
46 private readonly Dictionary<string, int> _tempBlocked;
47 private readonly System.Timers.Timer _forgetTimer;
48 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
49 private readonly System.Threading.ReaderWriterLockSlim _lockSlim = new System.Threading.ReaderWriterLockSlim();
50
51 protected BaseStreamHandlerBasicDOSProtector(string httpMethod, string path, BasicDosProtectorOptions options) : this(httpMethod, path, null, null, options) {}
52
53 protected BaseStreamHandlerBasicDOSProtector(string httpMethod, string path, string name, string description, BasicDosProtectorOptions options)
54 : base(httpMethod, path, name, description)
55 {
56 _generalRequestTimes = new CircularBuffer<int>(options.MaxRequestsInTimeframe + 1, true);
57 _generalRequestTimes.Put(0);
58 _options = options;
59 _deeperInspection = new Dictionary<string, CircularBuffer<int>>();
60 _tempBlocked = new Dictionary<string, int>();
61 _forgetTimer = new System.Timers.Timer();
62 _forgetTimer.Elapsed += delegate
63 {
64 _forgetTimer.Enabled = false;
65
66 List<string> removes = new List<string>();
67 _lockSlim.EnterReadLock();
68 foreach (string str in _tempBlocked.Keys)
69 {
70 if (
71 Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(),
72 _tempBlocked[str]) > 0)
73 removes.Add(str);
74 }
75 _lockSlim.ExitReadLock();
76 lock (_deeperInspection)
77 {
78 _lockSlim.EnterWriteLock();
79 for (int i = 0; i < removes.Count; i++)
80 {
81 _tempBlocked.Remove(removes[i]);
82 _deeperInspection.Remove(removes[i]);
83 }
84 _lockSlim.ExitWriteLock();
85 }
86 foreach (string str in removes)
87 {
88 m_log.InfoFormat("[{0}] client: {1} is no longer blocked.",
89 _options.ReportingName, str);
90 }
91 _lockSlim.EnterReadLock();
92 if (_tempBlocked.Count > 0)
93 _forgetTimer.Enabled = true;
94 _lockSlim.ExitReadLock();
95 };
96
97 _forgetTimer.Interval = _options.ForgetTimeSpan.TotalMilliseconds;
98 }
99
100 public virtual byte[] Handle(
101 string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
102 {
103 byte[] result;
104 RequestsReceived++;
105 //httpRequest.Headers
106
107 if (_options.MaxRequestsInTimeframe < 1 || _options.RequestTimeSpan.TotalMilliseconds < 1)
108 {
109 result = ProcessRequest(path, request, httpRequest, httpResponse);
110 RequestsHandled++;
111 return result;
112
113 }
114
115 string clientstring = GetClientString(httpRequest);
116
117 _lockSlim.EnterReadLock();
118 if (_tempBlocked.ContainsKey(clientstring))
119 {
120 _lockSlim.ExitReadLock();
121
122 if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod)
123 {
124 result = ThrottledRequest(path, request, httpRequest, httpResponse);
125 RequestsHandled++;
126 return result;
127 }
128 else
129 throw new System.Security.SecurityException("Throttled");
130 }
131 _lockSlim.ExitReadLock();
132
133 _generalRequestTimes.Put(Util.EnvironmentTickCount());
134
135 if (_generalRequestTimes.Size == _generalRequestTimes.Capacity &&
136 (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _generalRequestTimes.Get()) <
137 _options.RequestTimeSpan.TotalMilliseconds))
138 {
139 //Trigger deeper inspection
140 if (DeeperInspection(httpRequest))
141 {
142 result = ProcessRequest(path, request, httpRequest, httpResponse);
143 RequestsHandled++;
144 return result;
145 }
146 if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod)
147 {
148 result = ThrottledRequest(path, request, httpRequest, httpResponse);
149 RequestsHandled++;
150 return result;
151 }
152 else
153 throw new System.Security.SecurityException("Throttled");
154 }
155
156 result =ProcessRequest(path, request, httpRequest, httpResponse);
157 RequestsHandled++;
158
159 return result;
160 }
161
162 protected virtual byte[] ProcessRequest(
163 string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
164 {
165 return null;
166 }
167
168 protected virtual byte[] ThrottledRequest(
169 string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
170 {
171 return new byte[0];
172 }
173
174 private bool DeeperInspection(IOSHttpRequest httpRequest)
175 {
176 lock (_deeperInspection)
177 {
178 string clientstring = GetClientString(httpRequest);
179
180
181 if (_deeperInspection.ContainsKey(clientstring))
182 {
183 _deeperInspection[clientstring].Put(Util.EnvironmentTickCount());
184 if (_deeperInspection[clientstring].Size == _deeperInspection[clientstring].Capacity &&
185 (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _deeperInspection[clientstring].Get()) <
186 _options.RequestTimeSpan.TotalMilliseconds))
187 {
188 _lockSlim.EnterWriteLock();
189 if (!_tempBlocked.ContainsKey(clientstring))
190 _tempBlocked.Add(clientstring, Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds);
191 else
192 _tempBlocked[clientstring] = Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds;
193 _lockSlim.ExitWriteLock();
194
195 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, GetRemoteAddr(httpRequest));
196 return false;
197 }
198 //else
199 // return true;
200 }
201 else
202 {
203 _deeperInspection.Add(clientstring, new CircularBuffer<int>(_options.MaxRequestsInTimeframe + 1, true));
204 _deeperInspection[clientstring].Put(Util.EnvironmentTickCount());
205 _forgetTimer.Enabled = true;
206 }
207
208 }
209 return true;
210 }
211 private string GetRemoteAddr(IOSHttpRequest httpRequest)
212 {
213 string remoteaddr = string.Empty;
214 if (httpRequest.Headers["remote_addr"] != null)
215 remoteaddr = httpRequest.Headers["remote_addr"];
216
217 return remoteaddr;
218 }
219
220 private string GetClientString(IOSHttpRequest httpRequest)
221 {
222 string clientstring = string.Empty;
223
224 if (_options.AllowXForwardedFor && httpRequest.Headers["x-forwarded-for"] != null)
225 clientstring = httpRequest.Headers["x-forwarded-for"];
226 else
227 clientstring = GetRemoteAddr(httpRequest);
228
229 return clientstring;
230
231 }
232 }
233}
diff --git a/OpenSim/Framework/Servers/HttpServer/GenericHTTPBasicDOSProtector.cs b/OpenSim/Framework/Servers/HttpServer/GenericHTTPBasicDOSProtector.cs
new file mode 100644
index 0000000..5fc999a
--- /dev/null
+++ b/OpenSim/Framework/Servers/HttpServer/GenericHTTPBasicDOSProtector.cs
@@ -0,0 +1,238 @@
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
28using System;
29using System.Collections;
30using System.Collections.Generic;
31using System.Reflection;
32using System.Net;
33using OpenSim.Framework;
34using log4net;
35
36namespace OpenSim.Framework.Servers.HttpServer
37{
38 public class GenericHTTPDOSProtector
39 {
40 private readonly GenericHTTPMethod _normalMethod;
41 private readonly GenericHTTPMethod _throttledMethod;
42 private readonly CircularBuffer<int> _generalRequestTimes;
43 private readonly BasicDosProtectorOptions _options;
44 private readonly Dictionary<string, CircularBuffer<int>> _deeperInspection;
45 private readonly Dictionary<string, int> _tempBlocked;
46 private readonly System.Timers.Timer _forgetTimer;
47 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
48 private readonly System.Threading.ReaderWriterLockSlim _lockSlim = new System.Threading.ReaderWriterLockSlim();
49
50 public GenericHTTPDOSProtector(GenericHTTPMethod normalMethod, GenericHTTPMethod throttledMethod, BasicDosProtectorOptions options)
51 {
52 _normalMethod = normalMethod;
53 _throttledMethod = throttledMethod;
54 _generalRequestTimes = new CircularBuffer<int>(options.MaxRequestsInTimeframe + 1, true);
55 _generalRequestTimes.Put(0);
56 _options = options;
57 _deeperInspection = new Dictionary<string, CircularBuffer<int>>();
58 _tempBlocked = 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 _lockSlim.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 _lockSlim.ExitReadLock();
74 lock (_deeperInspection)
75 {
76 _lockSlim.EnterWriteLock();
77 for (int i = 0; i < removes.Count; i++)
78 {
79 _tempBlocked.Remove(removes[i]);
80 _deeperInspection.Remove(removes[i]);
81 }
82 _lockSlim.ExitWriteLock();
83 }
84 foreach (string str in removes)
85 {
86 m_log.InfoFormat("[{0}] client: {1} is no longer blocked.",
87 _options.ReportingName, str);
88 }
89 _lockSlim.EnterReadLock();
90 if (_tempBlocked.Count > 0)
91 _forgetTimer.Enabled = true;
92 _lockSlim.ExitReadLock();
93 };
94
95 _forgetTimer.Interval = _options.ForgetTimeSpan.TotalMilliseconds;
96 }
97 public Hashtable Process(Hashtable request)
98 {
99 if (_options.MaxRequestsInTimeframe < 1)
100 return _normalMethod(request);
101 if (_options.RequestTimeSpan.TotalMilliseconds < 1)
102 return _normalMethod(request);
103
104 string clientstring = GetClientString(request);
105
106 _lockSlim.EnterReadLock();
107 if (_tempBlocked.ContainsKey(clientstring))
108 {
109 _lockSlim.ExitReadLock();
110
111 if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod)
112 return _throttledMethod(request);
113 else
114 throw new System.Security.SecurityException("Throttled");
115 }
116 _lockSlim.ExitReadLock();
117
118 _generalRequestTimes.Put(Util.EnvironmentTickCount());
119
120 if (_generalRequestTimes.Size == _generalRequestTimes.Capacity &&
121 (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _generalRequestTimes.Get()) <
122 _options.RequestTimeSpan.TotalMilliseconds))
123 {
124 //Trigger deeper inspection
125 if (DeeperInspection(request))
126 return _normalMethod(request);
127 if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod)
128 return _throttledMethod(request);
129 else
130 throw new System.Security.SecurityException("Throttled");
131 }
132 Hashtable resp = null;
133 try
134 {
135 resp = _normalMethod(request);
136 }
137 catch (Exception)
138 {
139
140 throw;
141 }
142
143 return resp;
144 }
145 private bool DeeperInspection(Hashtable request)
146 {
147 lock (_deeperInspection)
148 {
149 string clientstring = GetClientString(request);
150
151
152 if (_deeperInspection.ContainsKey(clientstring))
153 {
154 _deeperInspection[clientstring].Put(Util.EnvironmentTickCount());
155 if (_deeperInspection[clientstring].Size == _deeperInspection[clientstring].Capacity &&
156 (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _deeperInspection[clientstring].Get()) <
157 _options.RequestTimeSpan.TotalMilliseconds))
158 {
159 _lockSlim.EnterWriteLock();
160 if (!_tempBlocked.ContainsKey(clientstring))
161 _tempBlocked.Add(clientstring, Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds);
162 else
163 _tempBlocked[clientstring] = Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds;
164 _lockSlim.ExitWriteLock();
165
166 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, GetRemoteAddr(request));
167 return false;
168 }
169 //else
170 // return true;
171 }
172 else
173 {
174 _deeperInspection.Add(clientstring, new CircularBuffer<int>(_options.MaxRequestsInTimeframe + 1, true));
175 _deeperInspection[clientstring].Put(Util.EnvironmentTickCount());
176 _forgetTimer.Enabled = true;
177 }
178
179 }
180 return true;
181 }
182
183 private string GetRemoteAddr(Hashtable request)
184 {
185 string remoteaddr = "";
186 if (!request.ContainsKey("headers"))
187 return remoteaddr;
188 Hashtable requestinfo = (Hashtable)request["headers"];
189 if (!requestinfo.ContainsKey("remote_addr"))
190 return remoteaddr;
191 object remote_addrobj = requestinfo["remote_addr"];
192 if (remote_addrobj != null)
193 {
194 if (!string.IsNullOrEmpty(remote_addrobj.ToString()))
195 {
196 remoteaddr = remote_addrobj.ToString();
197 }
198
199 }
200 return remoteaddr;
201 }
202
203 private string GetClientString(Hashtable request)
204 {
205 string clientstring = "";
206 if (!request.ContainsKey("headers"))
207 return clientstring;
208
209 Hashtable requestinfo = (Hashtable)request["headers"];
210 if (_options.AllowXForwardedFor && requestinfo.ContainsKey("x-forwarded-for"))
211 {
212 object str = requestinfo["x-forwarded-for"];
213 if (str != null)
214 {
215 if (!string.IsNullOrEmpty(str.ToString()))
216 {
217 return str.ToString();
218 }
219 }
220 }
221 if (!requestinfo.ContainsKey("remote_addr"))
222 return clientstring;
223
224 object remote_addrobj = requestinfo["remote_addr"];
225 if (remote_addrobj != null)
226 {
227 if (!string.IsNullOrEmpty(remote_addrobj.ToString()))
228 {
229 clientstring = remote_addrobj.ToString();
230 }
231 }
232
233 return clientstring;
234
235 }
236
237 }
238}
diff --git a/OpenSim/Framework/Servers/HttpServer/XmlRpcBasicDOSProtector.cs b/OpenSim/Framework/Servers/HttpServer/XmlRpcBasicDOSProtector.cs
new file mode 100644
index 0000000..ae59c95
--- /dev/null
+++ b/OpenSim/Framework/Servers/HttpServer/XmlRpcBasicDOSProtector.cs
@@ -0,0 +1,211 @@
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
28using System;
29using System.Collections.Generic;
30using System.Reflection;
31using System.Net;
32using Nwc.XmlRpc;
33using OpenSim.Framework;
34using log4net;
35
36namespace OpenSim.Framework.Servers.HttpServer
37{
38 public enum ThrottleAction
39 {
40 DoThrottledMethod,
41 DoThrow
42 }
43
44 public class XmlRpcBasicDOSProtector
45 {
46 private readonly XmlRpcMethod _normalMethod;
47 private readonly XmlRpcMethod _throttledMethod;
48 private readonly CircularBuffer<int> _generalRequestTimes; // General request checker
49 private readonly BasicDosProtectorOptions _options;
50 private readonly Dictionary<string, CircularBuffer<int>> _deeperInspection; // per client request checker
51 private readonly Dictionary<string, int> _tempBlocked; // blocked list
52 private readonly System.Timers.Timer _forgetTimer; // Cleanup timer
53 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
54 private readonly System.Threading.ReaderWriterLockSlim _lockSlim = new System.Threading.ReaderWriterLockSlim();
55
56 public XmlRpcBasicDOSProtector(XmlRpcMethod normalMethod, XmlRpcMethod throttledMethod,BasicDosProtectorOptions options)
57 {
58 _normalMethod = normalMethod;
59 _throttledMethod = throttledMethod;
60 _generalRequestTimes = new CircularBuffer<int>(options.MaxRequestsInTimeframe + 1,true);
61 _generalRequestTimes.Put(0);
62 _options = options;
63 _deeperInspection = new Dictionary<string, CircularBuffer<int>>();
64 _tempBlocked = new Dictionary<string, int>();
65 _forgetTimer = new System.Timers.Timer();
66 _forgetTimer.Elapsed += delegate
67 {
68 _forgetTimer.Enabled = false;
69
70 List<string> removes = new List<string>();
71 _lockSlim.EnterReadLock();
72 foreach (string str in _tempBlocked.Keys)
73 {
74 if (
75 Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(),
76 _tempBlocked[str]) > 0)
77 removes.Add(str);
78 }
79 _lockSlim.ExitReadLock();
80 lock (_deeperInspection)
81 {
82 _lockSlim.EnterWriteLock();
83 for (int i = 0; i < removes.Count; i++)
84 {
85 _tempBlocked.Remove(removes[i]);
86 _deeperInspection.Remove(removes[i]);
87 }
88 _lockSlim.ExitWriteLock();
89 }
90 foreach (string str in removes)
91 {
92 m_log.InfoFormat("[{0}] client: {1} is no longer blocked.",
93 _options.ReportingName, str);
94 }
95 _lockSlim.EnterReadLock();
96 if (_tempBlocked.Count > 0)
97 _forgetTimer.Enabled = true;
98 _lockSlim.ExitReadLock();
99 };
100
101 _forgetTimer.Interval = _options.ForgetTimeSpan.TotalMilliseconds;
102 }
103 public XmlRpcResponse Process(XmlRpcRequest request, IPEndPoint client)
104 {
105 // If these are set like this, this is disabled
106 if (_options.MaxRequestsInTimeframe < 1 || _options.RequestTimeSpan.TotalMilliseconds < 1)
107 return _normalMethod(request, client);
108
109 string clientstring = GetClientString(request, client);
110
111 _lockSlim.EnterReadLock();
112 if (_tempBlocked.ContainsKey(clientstring))
113 {
114 _lockSlim.ExitReadLock();
115
116 if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod)
117 return _throttledMethod(request, client);
118 else
119 throw new System.Security.SecurityException("Throttled");
120 }
121 _lockSlim.ExitReadLock();
122
123 _generalRequestTimes.Put(Util.EnvironmentTickCount());
124
125 if (_generalRequestTimes.Size == _generalRequestTimes.Capacity &&
126 (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _generalRequestTimes.Get()) <
127 _options.RequestTimeSpan.TotalMilliseconds))
128 {
129 //Trigger deeper inspection
130 if (DeeperInspection(request, client))
131 return _normalMethod(request, client);
132 if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod)
133 return _throttledMethod(request, client);
134 else
135 throw new System.Security.SecurityException("Throttled");
136 }
137 XmlRpcResponse resp = null;
138
139 resp = _normalMethod(request, client);
140
141 return resp;
142 }
143
144 // If the service is getting more hits per expected timeframe then it starts to separate them out by client
145 private bool DeeperInspection(XmlRpcRequest request, IPEndPoint client)
146 {
147 lock (_deeperInspection)
148 {
149 string clientstring = GetClientString(request, client);
150
151
152 if (_deeperInspection.ContainsKey(clientstring))
153 {
154 _deeperInspection[clientstring].Put(Util.EnvironmentTickCount());
155 if (_deeperInspection[clientstring].Size == _deeperInspection[clientstring].Capacity &&
156 (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _deeperInspection[clientstring].Get()) <
157 _options.RequestTimeSpan.TotalMilliseconds))
158 {
159 //Looks like we're over the limit
160 _lockSlim.EnterWriteLock();
161 if (!_tempBlocked.ContainsKey(clientstring))
162 _tempBlocked.Add(clientstring, Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds);
163 else
164 _tempBlocked[clientstring] = Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds;
165 _lockSlim.ExitWriteLock();
166
167 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, client.Address);
168
169 return false;
170 }
171 //else
172 // return true;
173 }
174 else
175 {
176 _deeperInspection.Add(clientstring, new CircularBuffer<int>(_options.MaxRequestsInTimeframe + 1, true));
177 _deeperInspection[clientstring].Put(Util.EnvironmentTickCount());
178 _forgetTimer.Enabled = true;
179 }
180
181 }
182 return true;
183 }
184 private string GetClientString(XmlRpcRequest request, IPEndPoint client)
185 {
186 string clientstring;
187 if (_options.AllowXForwardedFor && request.Params.Count > 3)
188 {
189 object headerstr = request.Params[3];
190 if (headerstr != null && !string.IsNullOrEmpty(headerstr.ToString()))
191 clientstring = request.Params[3].ToString();
192 else
193 clientstring = client.Address.ToString();
194 }
195 else
196 clientstring = client.Address.ToString();
197 return clientstring;
198 }
199
200 }
201
202 public class BasicDosProtectorOptions
203 {
204 public int MaxRequestsInTimeframe;
205 public TimeSpan RequestTimeSpan;
206 public TimeSpan ForgetTimeSpan;
207 public bool AllowXForwardedFor;
208 public string ReportingName = "BASICDOSPROTECTOR";
209 public ThrottleAction ThrottledAction = ThrottleAction.DoThrottledMethod;
210 }
211}