From f76cc6036ebf446553ee5201321879538dafe3b2 Mon Sep 17 00:00:00 2001 From: teravus Date: Mon, 7 Oct 2013 21:35:55 -0500 Subject: * Added a Basic DOS protection container/base object for the most common HTTP Server handlers. XMLRPC Handler, GenericHttpHandler and 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. --- OpenSim/Framework/CircularBuffer.cs | 312 +++++++++++++++++++++ .../BaseStreamHandlerBasicDOSProtector.cs | 233 +++++++++++++++ .../HttpServer/GenericHTTPBasicDOSProtector.cs | 238 ++++++++++++++++ .../Servers/HttpServer/XmlRpcBasicDOSProtector.cs | 211 ++++++++++++++ 4 files changed, 994 insertions(+) create mode 100644 OpenSim/Framework/CircularBuffer.cs create mode 100644 OpenSim/Framework/Servers/HttpServer/BaseStreamHandlerBasicDOSProtector.cs create mode 100644 OpenSim/Framework/Servers/HttpServer/GenericHTTPBasicDOSProtector.cs create mode 100644 OpenSim/Framework/Servers/HttpServer/XmlRpcBasicDOSProtector.cs (limited to 'OpenSim/Framework') 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 @@ +/* +Copyright (c) 2012, Alex Regueiro +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading; + +namespace OpenSim.Framework +{ + public class CircularBuffer : ICollection, IEnumerable, ICollection, IEnumerable + { + private int capacity; + private int size; + private int head; + private int tail; + private T[] buffer; + + [NonSerialized()] + private object syncRoot; + + public CircularBuffer(int capacity) + : this(capacity, false) + { + } + + public CircularBuffer(int capacity, bool allowOverflow) + { + if (capacity < 0) + throw new ArgumentException("Needs to have at least 1","capacity"); + + this.capacity = capacity; + size = 0; + head = 0; + tail = 0; + buffer = new T[capacity]; + AllowOverflow = allowOverflow; + } + + public bool AllowOverflow + { + get; + set; + } + + public int Capacity + { + get { return capacity; } + set + { + if (value == capacity) + return; + + if (value < size) + throw new ArgumentOutOfRangeException("value","Capacity is too small."); + + var dst = new T[value]; + if (size > 0) + CopyTo(dst); + buffer = dst; + + capacity = value; + } + } + + public int Size + { + get { return size; } + } + + public bool Contains(T item) + { + int bufferIndex = head; + var comparer = EqualityComparer.Default; + for (int i = 0; i < size; i++, bufferIndex++) + { + if (bufferIndex == capacity) + bufferIndex = 0; + + if (item == null && buffer[bufferIndex] == null) + return true; + else if ((buffer[bufferIndex] != null) && + comparer.Equals(buffer[bufferIndex], item)) + return true; + } + + return false; + } + + public void Clear() + { + size = 0; + head = 0; + tail = 0; + } + + public int Put(T[] src) + { + return Put(src, 0, src.Length); + } + + public int Put(T[] src, int offset, int count) + { + if (!AllowOverflow && count > capacity - size) + throw new InvalidOperationException("Buffer Overflow"); + + int srcIndex = offset; + for (int i = 0; i < count; i++, tail++, srcIndex++) + { + if (tail == capacity) + tail = 0; + buffer[tail] = src[srcIndex]; + } + size = Math.Min(size + count, capacity); + return count; + } + + public void Put(T item) + { + if (!AllowOverflow && size == capacity) + throw new InvalidOperationException("Buffer Overflow"); + + buffer[tail] = item; + if (++tail == capacity) + tail = 0; + size++; + } + + public void Skip(int count) + { + head += count; + if (head >= capacity) + head -= capacity; + } + + public T[] Get(int count) + { + var dst = new T[count]; + Get(dst); + return dst; + } + + public int Get(T[] dst) + { + return Get(dst, 0, dst.Length); + } + + public int Get(T[] dst, int offset, int count) + { + int realCount = Math.Min(count, size); + int dstIndex = offset; + for (int i = 0; i < realCount; i++, head++, dstIndex++) + { + if (head == capacity) + head = 0; + dst[dstIndex] = buffer[head]; + } + size -= realCount; + return realCount; + } + + public T Get() + { + if (size == 0) + throw new InvalidOperationException("Buffer Empty"); + + var item = buffer[head]; + if (++head == capacity) + head = 0; + size--; + return item; + } + + public void CopyTo(T[] array) + { + CopyTo(array, 0); + } + + public void CopyTo(T[] array, int arrayIndex) + { + CopyTo(0, array, arrayIndex, size); + } + + public void CopyTo(int index, T[] array, int arrayIndex, int count) + { + if (count > size) + throw new ArgumentOutOfRangeException("count", "Count Too Large"); + + int bufferIndex = head; + for (int i = 0; i < count; i++, bufferIndex++, arrayIndex++) + { + if (bufferIndex == capacity) + bufferIndex = 0; + array[arrayIndex] = buffer[bufferIndex]; + } + } + + public IEnumerator GetEnumerator() + { + int bufferIndex = head; + for (int i = 0; i < size; i++, bufferIndex++) + { + if (bufferIndex == capacity) + bufferIndex = 0; + + yield return buffer[bufferIndex]; + } + } + + public T[] GetBuffer() + { + return buffer; + } + + public T[] ToArray() + { + var dst = new T[size]; + CopyTo(dst); + return dst; + } + + #region ICollection Members + + int ICollection.Count + { + get { return Size; } + } + + bool ICollection.IsReadOnly + { + get { return false; } + } + + void ICollection.Add(T item) + { + Put(item); + } + + bool ICollection.Remove(T item) + { + if (size == 0) + return false; + + Get(); + return true; + } + + #endregion + + #region IEnumerable Members + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + #region ICollection Members + + int ICollection.Count + { + get { return Size; } + } + + bool ICollection.IsSynchronized + { + get { return false; } + } + + object ICollection.SyncRoot + { + get + { + if (syncRoot == null) + Interlocked.CompareExchange(ref syncRoot, new object(), null); + return syncRoot; + } + } + + void ICollection.CopyTo(Array array, int arrayIndex) + { + CopyTo((T[])array, arrayIndex); + } + + #endregion + + #region IEnumerable Members + + IEnumerator IEnumerable.GetEnumerator() + { + return (IEnumerator)GetEnumerator(); + } + + #endregion + } +} 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 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using OpenSim.Framework; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using log4net; + +namespace OpenSim.Framework.Servers.HttpServer +{ + /// + /// BaseStreamHandlerBasicDOSProtector Base streamed request handler. + /// + /// + /// Inheriting classes should override ProcessRequest() rather than Handle() + /// + public abstract class BaseStreamHandlerBasicDOSProtector : BaseRequestHandler, IStreamedRequestHandler + { + private readonly CircularBuffer _generalRequestTimes; + private readonly BasicDosProtectorOptions _options; + private readonly Dictionary> _deeperInspection; + private readonly Dictionary _tempBlocked; + private readonly System.Timers.Timer _forgetTimer; + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private readonly System.Threading.ReaderWriterLockSlim _lockSlim = new System.Threading.ReaderWriterLockSlim(); + + protected BaseStreamHandlerBasicDOSProtector(string httpMethod, string path, BasicDosProtectorOptions options) : this(httpMethod, path, null, null, options) {} + + protected BaseStreamHandlerBasicDOSProtector(string httpMethod, string path, string name, string description, BasicDosProtectorOptions options) + : base(httpMethod, path, name, description) + { + _generalRequestTimes = new CircularBuffer(options.MaxRequestsInTimeframe + 1, true); + _generalRequestTimes.Put(0); + _options = options; + _deeperInspection = new Dictionary>(); + _tempBlocked = new Dictionary(); + _forgetTimer = new System.Timers.Timer(); + _forgetTimer.Elapsed += delegate + { + _forgetTimer.Enabled = false; + + List removes = new List(); + _lockSlim.EnterReadLock(); + foreach (string str in _tempBlocked.Keys) + { + if ( + Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), + _tempBlocked[str]) > 0) + removes.Add(str); + } + _lockSlim.ExitReadLock(); + lock (_deeperInspection) + { + _lockSlim.EnterWriteLock(); + for (int i = 0; i < removes.Count; i++) + { + _tempBlocked.Remove(removes[i]); + _deeperInspection.Remove(removes[i]); + } + _lockSlim.ExitWriteLock(); + } + foreach (string str in removes) + { + m_log.InfoFormat("[{0}] client: {1} is no longer blocked.", + _options.ReportingName, str); + } + _lockSlim.EnterReadLock(); + if (_tempBlocked.Count > 0) + _forgetTimer.Enabled = true; + _lockSlim.ExitReadLock(); + }; + + _forgetTimer.Interval = _options.ForgetTimeSpan.TotalMilliseconds; + } + + public virtual byte[] Handle( + string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) + { + byte[] result; + RequestsReceived++; + //httpRequest.Headers + + if (_options.MaxRequestsInTimeframe < 1 || _options.RequestTimeSpan.TotalMilliseconds < 1) + { + result = ProcessRequest(path, request, httpRequest, httpResponse); + RequestsHandled++; + return result; + + } + + string clientstring = GetClientString(httpRequest); + + _lockSlim.EnterReadLock(); + if (_tempBlocked.ContainsKey(clientstring)) + { + _lockSlim.ExitReadLock(); + + if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod) + { + result = ThrottledRequest(path, request, httpRequest, httpResponse); + RequestsHandled++; + return result; + } + else + throw new System.Security.SecurityException("Throttled"); + } + _lockSlim.ExitReadLock(); + + _generalRequestTimes.Put(Util.EnvironmentTickCount()); + + if (_generalRequestTimes.Size == _generalRequestTimes.Capacity && + (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _generalRequestTimes.Get()) < + _options.RequestTimeSpan.TotalMilliseconds)) + { + //Trigger deeper inspection + if (DeeperInspection(httpRequest)) + { + result = ProcessRequest(path, request, httpRequest, httpResponse); + RequestsHandled++; + return result; + } + if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod) + { + result = ThrottledRequest(path, request, httpRequest, httpResponse); + RequestsHandled++; + return result; + } + else + throw new System.Security.SecurityException("Throttled"); + } + + result =ProcessRequest(path, request, httpRequest, httpResponse); + RequestsHandled++; + + return result; + } + + protected virtual byte[] ProcessRequest( + string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) + { + return null; + } + + protected virtual byte[] ThrottledRequest( + string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) + { + return new byte[0]; + } + + private bool DeeperInspection(IOSHttpRequest httpRequest) + { + lock (_deeperInspection) + { + string clientstring = GetClientString(httpRequest); + + + if (_deeperInspection.ContainsKey(clientstring)) + { + _deeperInspection[clientstring].Put(Util.EnvironmentTickCount()); + if (_deeperInspection[clientstring].Size == _deeperInspection[clientstring].Capacity && + (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _deeperInspection[clientstring].Get()) < + _options.RequestTimeSpan.TotalMilliseconds)) + { + _lockSlim.EnterWriteLock(); + if (!_tempBlocked.ContainsKey(clientstring)) + _tempBlocked.Add(clientstring, Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds); + else + _tempBlocked[clientstring] = Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds; + _lockSlim.ExitWriteLock(); + + 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)); + return false; + } + //else + // return true; + } + else + { + _deeperInspection.Add(clientstring, new CircularBuffer(_options.MaxRequestsInTimeframe + 1, true)); + _deeperInspection[clientstring].Put(Util.EnvironmentTickCount()); + _forgetTimer.Enabled = true; + } + + } + return true; + } + private string GetRemoteAddr(IOSHttpRequest httpRequest) + { + string remoteaddr = string.Empty; + if (httpRequest.Headers["remote_addr"] != null) + remoteaddr = httpRequest.Headers["remote_addr"]; + + return remoteaddr; + } + + private string GetClientString(IOSHttpRequest httpRequest) + { + string clientstring = string.Empty; + + if (_options.AllowXForwardedFor && httpRequest.Headers["x-forwarded-for"] != null) + clientstring = httpRequest.Headers["x-forwarded-for"]; + else + clientstring = GetRemoteAddr(httpRequest); + + return clientstring; + + } + } +} 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 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Net; +using OpenSim.Framework; +using log4net; + +namespace OpenSim.Framework.Servers.HttpServer +{ + public class GenericHTTPDOSProtector + { + private readonly GenericHTTPMethod _normalMethod; + private readonly GenericHTTPMethod _throttledMethod; + private readonly CircularBuffer _generalRequestTimes; + private readonly BasicDosProtectorOptions _options; + private readonly Dictionary> _deeperInspection; + private readonly Dictionary _tempBlocked; + private readonly System.Timers.Timer _forgetTimer; + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private readonly System.Threading.ReaderWriterLockSlim _lockSlim = new System.Threading.ReaderWriterLockSlim(); + + public GenericHTTPDOSProtector(GenericHTTPMethod normalMethod, GenericHTTPMethod throttledMethod, BasicDosProtectorOptions options) + { + _normalMethod = normalMethod; + _throttledMethod = throttledMethod; + _generalRequestTimes = new CircularBuffer(options.MaxRequestsInTimeframe + 1, true); + _generalRequestTimes.Put(0); + _options = options; + _deeperInspection = new Dictionary>(); + _tempBlocked = new Dictionary(); + _forgetTimer = new System.Timers.Timer(); + _forgetTimer.Elapsed += delegate + { + _forgetTimer.Enabled = false; + + List removes = new List(); + _lockSlim.EnterReadLock(); + foreach (string str in _tempBlocked.Keys) + { + if ( + Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), + _tempBlocked[str]) > 0) + removes.Add(str); + } + _lockSlim.ExitReadLock(); + lock (_deeperInspection) + { + _lockSlim.EnterWriteLock(); + for (int i = 0; i < removes.Count; i++) + { + _tempBlocked.Remove(removes[i]); + _deeperInspection.Remove(removes[i]); + } + _lockSlim.ExitWriteLock(); + } + foreach (string str in removes) + { + m_log.InfoFormat("[{0}] client: {1} is no longer blocked.", + _options.ReportingName, str); + } + _lockSlim.EnterReadLock(); + if (_tempBlocked.Count > 0) + _forgetTimer.Enabled = true; + _lockSlim.ExitReadLock(); + }; + + _forgetTimer.Interval = _options.ForgetTimeSpan.TotalMilliseconds; + } + public Hashtable Process(Hashtable request) + { + if (_options.MaxRequestsInTimeframe < 1) + return _normalMethod(request); + if (_options.RequestTimeSpan.TotalMilliseconds < 1) + return _normalMethod(request); + + string clientstring = GetClientString(request); + + _lockSlim.EnterReadLock(); + if (_tempBlocked.ContainsKey(clientstring)) + { + _lockSlim.ExitReadLock(); + + if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod) + return _throttledMethod(request); + else + throw new System.Security.SecurityException("Throttled"); + } + _lockSlim.ExitReadLock(); + + _generalRequestTimes.Put(Util.EnvironmentTickCount()); + + if (_generalRequestTimes.Size == _generalRequestTimes.Capacity && + (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _generalRequestTimes.Get()) < + _options.RequestTimeSpan.TotalMilliseconds)) + { + //Trigger deeper inspection + if (DeeperInspection(request)) + return _normalMethod(request); + if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod) + return _throttledMethod(request); + else + throw new System.Security.SecurityException("Throttled"); + } + Hashtable resp = null; + try + { + resp = _normalMethod(request); + } + catch (Exception) + { + + throw; + } + + return resp; + } + private bool DeeperInspection(Hashtable request) + { + lock (_deeperInspection) + { + string clientstring = GetClientString(request); + + + if (_deeperInspection.ContainsKey(clientstring)) + { + _deeperInspection[clientstring].Put(Util.EnvironmentTickCount()); + if (_deeperInspection[clientstring].Size == _deeperInspection[clientstring].Capacity && + (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _deeperInspection[clientstring].Get()) < + _options.RequestTimeSpan.TotalMilliseconds)) + { + _lockSlim.EnterWriteLock(); + if (!_tempBlocked.ContainsKey(clientstring)) + _tempBlocked.Add(clientstring, Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds); + else + _tempBlocked[clientstring] = Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds; + _lockSlim.ExitWriteLock(); + + 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)); + return false; + } + //else + // return true; + } + else + { + _deeperInspection.Add(clientstring, new CircularBuffer(_options.MaxRequestsInTimeframe + 1, true)); + _deeperInspection[clientstring].Put(Util.EnvironmentTickCount()); + _forgetTimer.Enabled = true; + } + + } + return true; + } + + private string GetRemoteAddr(Hashtable request) + { + string remoteaddr = ""; + if (!request.ContainsKey("headers")) + return remoteaddr; + Hashtable requestinfo = (Hashtable)request["headers"]; + if (!requestinfo.ContainsKey("remote_addr")) + return remoteaddr; + object remote_addrobj = requestinfo["remote_addr"]; + if (remote_addrobj != null) + { + if (!string.IsNullOrEmpty(remote_addrobj.ToString())) + { + remoteaddr = remote_addrobj.ToString(); + } + + } + return remoteaddr; + } + + private string GetClientString(Hashtable request) + { + string clientstring = ""; + if (!request.ContainsKey("headers")) + return clientstring; + + Hashtable requestinfo = (Hashtable)request["headers"]; + if (_options.AllowXForwardedFor && requestinfo.ContainsKey("x-forwarded-for")) + { + object str = requestinfo["x-forwarded-for"]; + if (str != null) + { + if (!string.IsNullOrEmpty(str.ToString())) + { + return str.ToString(); + } + } + } + if (!requestinfo.ContainsKey("remote_addr")) + return clientstring; + + object remote_addrobj = requestinfo["remote_addr"]; + if (remote_addrobj != null) + { + if (!string.IsNullOrEmpty(remote_addrobj.ToString())) + { + clientstring = remote_addrobj.ToString(); + } + } + + return clientstring; + + } + + } +} 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 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Net; +using Nwc.XmlRpc; +using OpenSim.Framework; +using log4net; + +namespace OpenSim.Framework.Servers.HttpServer +{ + public enum ThrottleAction + { + DoThrottledMethod, + DoThrow + } + + public class XmlRpcBasicDOSProtector + { + private readonly XmlRpcMethod _normalMethod; + private readonly XmlRpcMethod _throttledMethod; + private readonly CircularBuffer _generalRequestTimes; // General request checker + private readonly BasicDosProtectorOptions _options; + private readonly Dictionary> _deeperInspection; // per client request checker + private readonly Dictionary _tempBlocked; // blocked list + private readonly System.Timers.Timer _forgetTimer; // Cleanup timer + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private readonly System.Threading.ReaderWriterLockSlim _lockSlim = new System.Threading.ReaderWriterLockSlim(); + + public XmlRpcBasicDOSProtector(XmlRpcMethod normalMethod, XmlRpcMethod throttledMethod,BasicDosProtectorOptions options) + { + _normalMethod = normalMethod; + _throttledMethod = throttledMethod; + _generalRequestTimes = new CircularBuffer(options.MaxRequestsInTimeframe + 1,true); + _generalRequestTimes.Put(0); + _options = options; + _deeperInspection = new Dictionary>(); + _tempBlocked = new Dictionary(); + _forgetTimer = new System.Timers.Timer(); + _forgetTimer.Elapsed += delegate + { + _forgetTimer.Enabled = false; + + List removes = new List(); + _lockSlim.EnterReadLock(); + foreach (string str in _tempBlocked.Keys) + { + if ( + Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), + _tempBlocked[str]) > 0) + removes.Add(str); + } + _lockSlim.ExitReadLock(); + lock (_deeperInspection) + { + _lockSlim.EnterWriteLock(); + for (int i = 0; i < removes.Count; i++) + { + _tempBlocked.Remove(removes[i]); + _deeperInspection.Remove(removes[i]); + } + _lockSlim.ExitWriteLock(); + } + foreach (string str in removes) + { + m_log.InfoFormat("[{0}] client: {1} is no longer blocked.", + _options.ReportingName, str); + } + _lockSlim.EnterReadLock(); + if (_tempBlocked.Count > 0) + _forgetTimer.Enabled = true; + _lockSlim.ExitReadLock(); + }; + + _forgetTimer.Interval = _options.ForgetTimeSpan.TotalMilliseconds; + } + public XmlRpcResponse Process(XmlRpcRequest request, IPEndPoint client) + { + // If these are set like this, this is disabled + if (_options.MaxRequestsInTimeframe < 1 || _options.RequestTimeSpan.TotalMilliseconds < 1) + return _normalMethod(request, client); + + string clientstring = GetClientString(request, client); + + _lockSlim.EnterReadLock(); + if (_tempBlocked.ContainsKey(clientstring)) + { + _lockSlim.ExitReadLock(); + + if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod) + return _throttledMethod(request, client); + else + throw new System.Security.SecurityException("Throttled"); + } + _lockSlim.ExitReadLock(); + + _generalRequestTimes.Put(Util.EnvironmentTickCount()); + + if (_generalRequestTimes.Size == _generalRequestTimes.Capacity && + (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _generalRequestTimes.Get()) < + _options.RequestTimeSpan.TotalMilliseconds)) + { + //Trigger deeper inspection + if (DeeperInspection(request, client)) + return _normalMethod(request, client); + if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod) + return _throttledMethod(request, client); + else + throw new System.Security.SecurityException("Throttled"); + } + XmlRpcResponse resp = null; + + resp = _normalMethod(request, client); + + return resp; + } + + // If the service is getting more hits per expected timeframe then it starts to separate them out by client + private bool DeeperInspection(XmlRpcRequest request, IPEndPoint client) + { + lock (_deeperInspection) + { + string clientstring = GetClientString(request, client); + + + if (_deeperInspection.ContainsKey(clientstring)) + { + _deeperInspection[clientstring].Put(Util.EnvironmentTickCount()); + if (_deeperInspection[clientstring].Size == _deeperInspection[clientstring].Capacity && + (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _deeperInspection[clientstring].Get()) < + _options.RequestTimeSpan.TotalMilliseconds)) + { + //Looks like we're over the limit + _lockSlim.EnterWriteLock(); + if (!_tempBlocked.ContainsKey(clientstring)) + _tempBlocked.Add(clientstring, Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds); + else + _tempBlocked[clientstring] = Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds; + _lockSlim.ExitWriteLock(); + + 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); + + return false; + } + //else + // return true; + } + else + { + _deeperInspection.Add(clientstring, new CircularBuffer(_options.MaxRequestsInTimeframe + 1, true)); + _deeperInspection[clientstring].Put(Util.EnvironmentTickCount()); + _forgetTimer.Enabled = true; + } + + } + return true; + } + private string GetClientString(XmlRpcRequest request, IPEndPoint client) + { + string clientstring; + if (_options.AllowXForwardedFor && request.Params.Count > 3) + { + object headerstr = request.Params[3]; + if (headerstr != null && !string.IsNullOrEmpty(headerstr.ToString())) + clientstring = request.Params[3].ToString(); + else + clientstring = client.Address.ToString(); + } + else + clientstring = client.Address.ToString(); + return clientstring; + } + + } + + public class BasicDosProtectorOptions + { + public int MaxRequestsInTimeframe; + public TimeSpan RequestTimeSpan; + public TimeSpan ForgetTimeSpan; + public bool AllowXForwardedFor; + public string ReportingName = "BASICDOSPROTECTOR"; + public ThrottleAction ThrottledAction = ThrottleAction.DoThrottledMethod; + } +} -- cgit v1.1 From 75fdd6054d6605877acb511b1bd794de963a15f3 Mon Sep 17 00:00:00 2001 From: teravus Date: Mon, 7 Oct 2013 23:19:50 -0500 Subject: * Refactor * Break out common BasicDOSProtector code into separate class. --- .../BaseStreamHandlerBasicDOSProtector.cs | 144 +--------------- .../Servers/HttpServer/BasicDOSProtector.cs | 181 +++++++++++++++++++++ .../HttpServer/GenericHTTPBasicDOSProtector.cs | 143 +--------------- .../Servers/HttpServer/XmlRpcBasicDOSProtector.cs | 155 ++---------------- 4 files changed, 213 insertions(+), 410 deletions(-) create mode 100644 OpenSim/Framework/Servers/HttpServer/BasicDOSProtector.cs (limited to 'OpenSim/Framework') diff --git a/OpenSim/Framework/Servers/HttpServer/BaseStreamHandlerBasicDOSProtector.cs b/OpenSim/Framework/Servers/HttpServer/BaseStreamHandlerBasicDOSProtector.cs index 8fc9a8a..9b8b8c2 100644 --- a/OpenSim/Framework/Servers/HttpServer/BaseStreamHandlerBasicDOSProtector.cs +++ b/OpenSim/Framework/Servers/HttpServer/BaseStreamHandlerBasicDOSProtector.cs @@ -25,10 +25,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using OpenSim.Framework; -using System.Collections.Generic; using System.IO; -using System.Reflection; -using log4net; namespace OpenSim.Framework.Servers.HttpServer { @@ -40,61 +37,17 @@ namespace OpenSim.Framework.Servers.HttpServer /// public abstract class BaseStreamHandlerBasicDOSProtector : BaseRequestHandler, IStreamedRequestHandler { - private readonly CircularBuffer _generalRequestTimes; + private readonly BasicDosProtectorOptions _options; - private readonly Dictionary> _deeperInspection; - private readonly Dictionary _tempBlocked; - private readonly System.Timers.Timer _forgetTimer; - private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - private readonly System.Threading.ReaderWriterLockSlim _lockSlim = new System.Threading.ReaderWriterLockSlim(); + private readonly BasicDOSProtector _dosProtector; protected BaseStreamHandlerBasicDOSProtector(string httpMethod, string path, BasicDosProtectorOptions options) : this(httpMethod, path, null, null, options) {} protected BaseStreamHandlerBasicDOSProtector(string httpMethod, string path, string name, string description, BasicDosProtectorOptions options) : base(httpMethod, path, name, description) { - _generalRequestTimes = new CircularBuffer(options.MaxRequestsInTimeframe + 1, true); - _generalRequestTimes.Put(0); _options = options; - _deeperInspection = new Dictionary>(); - _tempBlocked = new Dictionary(); - _forgetTimer = new System.Timers.Timer(); - _forgetTimer.Elapsed += delegate - { - _forgetTimer.Enabled = false; - - List removes = new List(); - _lockSlim.EnterReadLock(); - foreach (string str in _tempBlocked.Keys) - { - if ( - Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), - _tempBlocked[str]) > 0) - removes.Add(str); - } - _lockSlim.ExitReadLock(); - lock (_deeperInspection) - { - _lockSlim.EnterWriteLock(); - for (int i = 0; i < removes.Count; i++) - { - _tempBlocked.Remove(removes[i]); - _deeperInspection.Remove(removes[i]); - } - _lockSlim.ExitWriteLock(); - } - foreach (string str in removes) - { - m_log.InfoFormat("[{0}] client: {1} is no longer blocked.", - _options.ReportingName, str); - } - _lockSlim.EnterReadLock(); - if (_tempBlocked.Count > 0) - _forgetTimer.Enabled = true; - _lockSlim.ExitReadLock(); - }; - - _forgetTimer.Interval = _options.ForgetTimeSpan.TotalMilliseconds; + _dosProtector = new BasicDOSProtector(_options); } public virtual byte[] Handle( @@ -102,58 +55,13 @@ namespace OpenSim.Framework.Servers.HttpServer { byte[] result; RequestsReceived++; - //httpRequest.Headers - if (_options.MaxRequestsInTimeframe < 1 || _options.RequestTimeSpan.TotalMilliseconds < 1) - { + if (_dosProtector.Process(GetClientString(httpRequest), GetRemoteAddr(httpRequest))) result = ProcessRequest(path, request, httpRequest, httpResponse); - RequestsHandled++; - return result; - - } - - string clientstring = GetClientString(httpRequest); - - _lockSlim.EnterReadLock(); - if (_tempBlocked.ContainsKey(clientstring)) - { - _lockSlim.ExitReadLock(); - - if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod) - { - result = ThrottledRequest(path, request, httpRequest, httpResponse); - RequestsHandled++; - return result; - } - else - throw new System.Security.SecurityException("Throttled"); - } - _lockSlim.ExitReadLock(); - - _generalRequestTimes.Put(Util.EnvironmentTickCount()); + else + result = ThrottledRequest(path, request, httpRequest, httpResponse); - if (_generalRequestTimes.Size == _generalRequestTimes.Capacity && - (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _generalRequestTimes.Get()) < - _options.RequestTimeSpan.TotalMilliseconds)) - { - //Trigger deeper inspection - if (DeeperInspection(httpRequest)) - { - result = ProcessRequest(path, request, httpRequest, httpResponse); - RequestsHandled++; - return result; - } - if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod) - { - result = ThrottledRequest(path, request, httpRequest, httpResponse); - RequestsHandled++; - return result; - } - else - throw new System.Security.SecurityException("Throttled"); - } - - result =ProcessRequest(path, request, httpRequest, httpResponse); + RequestsHandled++; return result; @@ -171,43 +79,7 @@ namespace OpenSim.Framework.Servers.HttpServer return new byte[0]; } - private bool DeeperInspection(IOSHttpRequest httpRequest) - { - lock (_deeperInspection) - { - string clientstring = GetClientString(httpRequest); - - - if (_deeperInspection.ContainsKey(clientstring)) - { - _deeperInspection[clientstring].Put(Util.EnvironmentTickCount()); - if (_deeperInspection[clientstring].Size == _deeperInspection[clientstring].Capacity && - (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _deeperInspection[clientstring].Get()) < - _options.RequestTimeSpan.TotalMilliseconds)) - { - _lockSlim.EnterWriteLock(); - if (!_tempBlocked.ContainsKey(clientstring)) - _tempBlocked.Add(clientstring, Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds); - else - _tempBlocked[clientstring] = Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds; - _lockSlim.ExitWriteLock(); - - 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)); - return false; - } - //else - // return true; - } - else - { - _deeperInspection.Add(clientstring, new CircularBuffer(_options.MaxRequestsInTimeframe + 1, true)); - _deeperInspection[clientstring].Put(Util.EnvironmentTickCount()); - _forgetTimer.Enabled = true; - } - - } - return true; - } + private string GetRemoteAddr(IOSHttpRequest httpRequest) { string remoteaddr = string.Empty; diff --git a/OpenSim/Framework/Servers/HttpServer/BasicDOSProtector.cs b/OpenSim/Framework/Servers/HttpServer/BasicDOSProtector.cs new file mode 100644 index 0000000..50a4ea2 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/BasicDOSProtector.cs @@ -0,0 +1,181 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using System; +using System.Collections.Generic; +using System.Reflection; +using log4net; + +namespace OpenSim.Framework.Servers.HttpServer +{ + + public class BasicDOSProtector + { + public enum ThrottleAction + { + DoThrottledMethod, + DoThrow + } + private readonly CircularBuffer _generalRequestTimes; // General request checker + private readonly BasicDosProtectorOptions _options; + private readonly Dictionary> _deeperInspection; // per client request checker + private readonly Dictionary _tempBlocked; // blocked list + private readonly System.Timers.Timer _forgetTimer; // Cleanup timer + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private readonly System.Threading.ReaderWriterLockSlim _lockSlim = new System.Threading.ReaderWriterLockSlim(); + public BasicDOSProtector(BasicDosProtectorOptions options) + { + _generalRequestTimes = new CircularBuffer(options.MaxRequestsInTimeframe + 1, true); + _generalRequestTimes.Put(0); + _options = options; + _deeperInspection = new Dictionary>(); + _tempBlocked = new Dictionary(); + _forgetTimer = new System.Timers.Timer(); + _forgetTimer.Elapsed += delegate + { + _forgetTimer.Enabled = false; + + List removes = new List(); + _lockSlim.EnterReadLock(); + foreach (string str in _tempBlocked.Keys) + { + if ( + Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), + _tempBlocked[str]) > 0) + removes.Add(str); + } + _lockSlim.ExitReadLock(); + lock (_deeperInspection) + { + _lockSlim.EnterWriteLock(); + for (int i = 0; i < removes.Count; i++) + { + _tempBlocked.Remove(removes[i]); + _deeperInspection.Remove(removes[i]); + } + _lockSlim.ExitWriteLock(); + } + foreach (string str in removes) + { + m_log.InfoFormat("[{0}] client: {1} is no longer blocked.", + _options.ReportingName, str); + } + _lockSlim.EnterReadLock(); + if (_tempBlocked.Count > 0) + _forgetTimer.Enabled = true; + _lockSlim.ExitReadLock(); + }; + + _forgetTimer.Interval = _options.ForgetTimeSpan.TotalMilliseconds; + } + public bool Process(string key, string endpoint) + { + if (_options.MaxRequestsInTimeframe < 1 || _options.RequestTimeSpan.TotalMilliseconds < 1) + return true; + + string clientstring = key; + + _lockSlim.EnterReadLock(); + if (_tempBlocked.ContainsKey(clientstring)) + { + _lockSlim.ExitReadLock(); + + if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod) + return false; + else + throw new System.Security.SecurityException("Throttled"); + } + _lockSlim.ExitReadLock(); + + _generalRequestTimes.Put(Util.EnvironmentTickCount()); + + if (_generalRequestTimes.Size == _generalRequestTimes.Capacity && + (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _generalRequestTimes.Get()) < + _options.RequestTimeSpan.TotalMilliseconds)) + { + //Trigger deeper inspection + if (DeeperInspection(key, endpoint)) + return true; + if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod) + return false; + else + throw new System.Security.SecurityException("Throttled"); + } + return true; + } + private bool DeeperInspection(string key, string endpoint) + { + lock (_deeperInspection) + { + string clientstring = key; + + + if (_deeperInspection.ContainsKey(clientstring)) + { + _deeperInspection[clientstring].Put(Util.EnvironmentTickCount()); + if (_deeperInspection[clientstring].Size == _deeperInspection[clientstring].Capacity && + (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _deeperInspection[clientstring].Get()) < + _options.RequestTimeSpan.TotalMilliseconds)) + { + //Looks like we're over the limit + _lockSlim.EnterWriteLock(); + if (!_tempBlocked.ContainsKey(clientstring)) + _tempBlocked.Add(clientstring, Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds); + else + _tempBlocked[clientstring] = Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds; + _lockSlim.ExitWriteLock(); + + 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); + + return false; + } + //else + // return true; + } + else + { + _deeperInspection.Add(clientstring, new CircularBuffer(_options.MaxRequestsInTimeframe + 1, true)); + _deeperInspection[clientstring].Put(Util.EnvironmentTickCount()); + _forgetTimer.Enabled = true; + } + + } + return true; + } + + } + + + public class BasicDosProtectorOptions + { + public int MaxRequestsInTimeframe; + public TimeSpan RequestTimeSpan; + public TimeSpan ForgetTimeSpan; + public bool AllowXForwardedFor; + public string ReportingName = "BASICDOSPROTECTOR"; + public BasicDOSProtector.ThrottleAction ThrottledAction = BasicDOSProtector.ThrottleAction.DoThrottledMethod; + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/GenericHTTPBasicDOSProtector.cs b/OpenSim/Framework/Servers/HttpServer/GenericHTTPBasicDOSProtector.cs index 5fc999a..39c98b4 100644 --- a/OpenSim/Framework/Servers/HttpServer/GenericHTTPBasicDOSProtector.cs +++ b/OpenSim/Framework/Servers/HttpServer/GenericHTTPBasicDOSProtector.cs @@ -25,13 +25,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -using System; using System.Collections; -using System.Collections.Generic; -using System.Reflection; -using System.Net; -using OpenSim.Framework; -using log4net; namespace OpenSim.Framework.Servers.HttpServer { @@ -39,147 +33,26 @@ namespace OpenSim.Framework.Servers.HttpServer { private readonly GenericHTTPMethod _normalMethod; private readonly GenericHTTPMethod _throttledMethod; - private readonly CircularBuffer _generalRequestTimes; + private readonly BasicDosProtectorOptions _options; - private readonly Dictionary> _deeperInspection; - private readonly Dictionary _tempBlocked; - private readonly System.Timers.Timer _forgetTimer; - private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - private readonly System.Threading.ReaderWriterLockSlim _lockSlim = new System.Threading.ReaderWriterLockSlim(); + private readonly BasicDOSProtector _dosProtector; public GenericHTTPDOSProtector(GenericHTTPMethod normalMethod, GenericHTTPMethod throttledMethod, BasicDosProtectorOptions options) { _normalMethod = normalMethod; _throttledMethod = throttledMethod; - _generalRequestTimes = new CircularBuffer(options.MaxRequestsInTimeframe + 1, true); - _generalRequestTimes.Put(0); + _options = options; - _deeperInspection = new Dictionary>(); - _tempBlocked = new Dictionary(); - _forgetTimer = new System.Timers.Timer(); - _forgetTimer.Elapsed += delegate - { - _forgetTimer.Enabled = false; - - List removes = new List(); - _lockSlim.EnterReadLock(); - foreach (string str in _tempBlocked.Keys) - { - if ( - Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), - _tempBlocked[str]) > 0) - removes.Add(str); - } - _lockSlim.ExitReadLock(); - lock (_deeperInspection) - { - _lockSlim.EnterWriteLock(); - for (int i = 0; i < removes.Count; i++) - { - _tempBlocked.Remove(removes[i]); - _deeperInspection.Remove(removes[i]); - } - _lockSlim.ExitWriteLock(); - } - foreach (string str in removes) - { - m_log.InfoFormat("[{0}] client: {1} is no longer blocked.", - _options.ReportingName, str); - } - _lockSlim.EnterReadLock(); - if (_tempBlocked.Count > 0) - _forgetTimer.Enabled = true; - _lockSlim.ExitReadLock(); - }; - - _forgetTimer.Interval = _options.ForgetTimeSpan.TotalMilliseconds; + _dosProtector = new BasicDOSProtector(_options); } public Hashtable Process(Hashtable request) { - if (_options.MaxRequestsInTimeframe < 1) + if (_dosProtector.Process(GetClientString(request), GetRemoteAddr(request))) return _normalMethod(request); - if (_options.RequestTimeSpan.TotalMilliseconds < 1) - return _normalMethod(request); - - string clientstring = GetClientString(request); - - _lockSlim.EnterReadLock(); - if (_tempBlocked.ContainsKey(clientstring)) - { - _lockSlim.ExitReadLock(); - - if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod) - return _throttledMethod(request); - else - throw new System.Security.SecurityException("Throttled"); - } - _lockSlim.ExitReadLock(); - - _generalRequestTimes.Put(Util.EnvironmentTickCount()); - - if (_generalRequestTimes.Size == _generalRequestTimes.Capacity && - (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _generalRequestTimes.Get()) < - _options.RequestTimeSpan.TotalMilliseconds)) - { - //Trigger deeper inspection - if (DeeperInspection(request)) - return _normalMethod(request); - if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod) - return _throttledMethod(request); - else - throw new System.Security.SecurityException("Throttled"); - } - Hashtable resp = null; - try - { - resp = _normalMethod(request); - } - catch (Exception) - { - - throw; - } - - return resp; - } - private bool DeeperInspection(Hashtable request) - { - lock (_deeperInspection) - { - string clientstring = GetClientString(request); - - - if (_deeperInspection.ContainsKey(clientstring)) - { - _deeperInspection[clientstring].Put(Util.EnvironmentTickCount()); - if (_deeperInspection[clientstring].Size == _deeperInspection[clientstring].Capacity && - (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _deeperInspection[clientstring].Get()) < - _options.RequestTimeSpan.TotalMilliseconds)) - { - _lockSlim.EnterWriteLock(); - if (!_tempBlocked.ContainsKey(clientstring)) - _tempBlocked.Add(clientstring, Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds); - else - _tempBlocked[clientstring] = Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds; - _lockSlim.ExitWriteLock(); - - 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)); - return false; - } - //else - // return true; - } - else - { - _deeperInspection.Add(clientstring, new CircularBuffer(_options.MaxRequestsInTimeframe + 1, true)); - _deeperInspection[clientstring].Put(Util.EnvironmentTickCount()); - _forgetTimer.Enabled = true; - } - - } - return true; + else + return _throttledMethod(request); } - + private string GetRemoteAddr(Hashtable request) { string remoteaddr = ""; diff --git a/OpenSim/Framework/Servers/HttpServer/XmlRpcBasicDOSProtector.cs b/OpenSim/Framework/Servers/HttpServer/XmlRpcBasicDOSProtector.cs index ae59c95..bc7efb6 100644 --- a/OpenSim/Framework/Servers/HttpServer/XmlRpcBasicDOSProtector.cs +++ b/OpenSim/Framework/Servers/HttpServer/XmlRpcBasicDOSProtector.cs @@ -25,162 +25,42 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -using System; -using System.Collections.Generic; -using System.Reflection; using System.Net; using Nwc.XmlRpc; using OpenSim.Framework; -using log4net; + namespace OpenSim.Framework.Servers.HttpServer { - public enum ThrottleAction - { - DoThrottledMethod, - DoThrow - } - public class XmlRpcBasicDOSProtector { private readonly XmlRpcMethod _normalMethod; private readonly XmlRpcMethod _throttledMethod; - private readonly CircularBuffer _generalRequestTimes; // General request checker + private readonly BasicDosProtectorOptions _options; - private readonly Dictionary> _deeperInspection; // per client request checker - private readonly Dictionary _tempBlocked; // blocked list - private readonly System.Timers.Timer _forgetTimer; // Cleanup timer - private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - private readonly System.Threading.ReaderWriterLockSlim _lockSlim = new System.Threading.ReaderWriterLockSlim(); + private readonly BasicDOSProtector _dosProtector; public XmlRpcBasicDOSProtector(XmlRpcMethod normalMethod, XmlRpcMethod throttledMethod,BasicDosProtectorOptions options) { _normalMethod = normalMethod; _throttledMethod = throttledMethod; - _generalRequestTimes = new CircularBuffer(options.MaxRequestsInTimeframe + 1,true); - _generalRequestTimes.Put(0); + _options = options; - _deeperInspection = new Dictionary>(); - _tempBlocked = new Dictionary(); - _forgetTimer = new System.Timers.Timer(); - _forgetTimer.Elapsed += delegate - { - _forgetTimer.Enabled = false; + _dosProtector = new BasicDOSProtector(_options); - List removes = new List(); - _lockSlim.EnterReadLock(); - foreach (string str in _tempBlocked.Keys) - { - if ( - Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), - _tempBlocked[str]) > 0) - removes.Add(str); - } - _lockSlim.ExitReadLock(); - lock (_deeperInspection) - { - _lockSlim.EnterWriteLock(); - for (int i = 0; i < removes.Count; i++) - { - _tempBlocked.Remove(removes[i]); - _deeperInspection.Remove(removes[i]); - } - _lockSlim.ExitWriteLock(); - } - foreach (string str in removes) - { - m_log.InfoFormat("[{0}] client: {1} is no longer blocked.", - _options.ReportingName, str); - } - _lockSlim.EnterReadLock(); - if (_tempBlocked.Count > 0) - _forgetTimer.Enabled = true; - _lockSlim.ExitReadLock(); - }; - - _forgetTimer.Interval = _options.ForgetTimeSpan.TotalMilliseconds; } public XmlRpcResponse Process(XmlRpcRequest request, IPEndPoint client) { - // If these are set like this, this is disabled - if (_options.MaxRequestsInTimeframe < 1 || _options.RequestTimeSpan.TotalMilliseconds < 1) - return _normalMethod(request, client); - - string clientstring = GetClientString(request, client); - - _lockSlim.EnterReadLock(); - if (_tempBlocked.ContainsKey(clientstring)) - { - _lockSlim.ExitReadLock(); - if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod) - return _throttledMethod(request, client); - else - throw new System.Security.SecurityException("Throttled"); - } - _lockSlim.ExitReadLock(); - - _generalRequestTimes.Put(Util.EnvironmentTickCount()); - - if (_generalRequestTimes.Size == _generalRequestTimes.Capacity && - (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _generalRequestTimes.Get()) < - _options.RequestTimeSpan.TotalMilliseconds)) - { - //Trigger deeper inspection - if (DeeperInspection(request, client)) - return _normalMethod(request, client); - if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod) - return _throttledMethod(request, client); - else - throw new System.Security.SecurityException("Throttled"); - } XmlRpcResponse resp = null; - - resp = _normalMethod(request, client); - + if (_dosProtector.Process(GetClientString(request, client), GetEndPoint(request, client))) + resp = _normalMethod(request, client); + else + resp = _throttledMethod(request, client); + return resp; } - // If the service is getting more hits per expected timeframe then it starts to separate them out by client - private bool DeeperInspection(XmlRpcRequest request, IPEndPoint client) - { - lock (_deeperInspection) - { - string clientstring = GetClientString(request, client); - - - if (_deeperInspection.ContainsKey(clientstring)) - { - _deeperInspection[clientstring].Put(Util.EnvironmentTickCount()); - if (_deeperInspection[clientstring].Size == _deeperInspection[clientstring].Capacity && - (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _deeperInspection[clientstring].Get()) < - _options.RequestTimeSpan.TotalMilliseconds)) - { - //Looks like we're over the limit - _lockSlim.EnterWriteLock(); - if (!_tempBlocked.ContainsKey(clientstring)) - _tempBlocked.Add(clientstring, Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds); - else - _tempBlocked[clientstring] = Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds; - _lockSlim.ExitWriteLock(); - - 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); - - return false; - } - //else - // return true; - } - else - { - _deeperInspection.Add(clientstring, new CircularBuffer(_options.MaxRequestsInTimeframe + 1, true)); - _deeperInspection[clientstring].Put(Util.EnvironmentTickCount()); - _forgetTimer.Enabled = true; - } - - } - return true; - } private string GetClientString(XmlRpcRequest request, IPEndPoint client) { string clientstring; @@ -197,15 +77,12 @@ namespace OpenSim.Framework.Servers.HttpServer return clientstring; } - } + private string GetEndPoint(XmlRpcRequest request, IPEndPoint client) + { + return client.Address.ToString(); + } - public class BasicDosProtectorOptions - { - public int MaxRequestsInTimeframe; - public TimeSpan RequestTimeSpan; - public TimeSpan ForgetTimeSpan; - public bool AllowXForwardedFor; - public string ReportingName = "BASICDOSPROTECTOR"; - public ThrottleAction ThrottledAction = ThrottleAction.DoThrottledMethod; } + + } -- cgit v1.1 From 1df58d04b16601fb3afa408ef48e59aa68dc335b Mon Sep 17 00:00:00 2001 From: teravus Date: Mon, 7 Oct 2013 23:48:24 -0500 Subject: * Move the BasicDOSProtector.cs to OpenSim.Framework (all useful classes belong there.....) * Add an IsBlocked(string Key) method so it can be used more generically. (think.. if we want to rate limit login failures, we could have a call in the Login Service to IsBlocked(uuid.ToString()) and ignore the connection if it returns true, if IsBlocked returns false, we could run the login information and if the login fails we could run the Process method to count the login failures. --- OpenSim/Framework/BasicDOSProtector.cs | 209 +++++++++++++++++++++ .../Servers/HttpServer/BasicDOSProtector.cs | 181 ------------------ 2 files changed, 209 insertions(+), 181 deletions(-) create mode 100644 OpenSim/Framework/BasicDOSProtector.cs delete mode 100644 OpenSim/Framework/Servers/HttpServer/BasicDOSProtector.cs (limited to 'OpenSim/Framework') diff --git a/OpenSim/Framework/BasicDOSProtector.cs b/OpenSim/Framework/BasicDOSProtector.cs new file mode 100644 index 0000000..b470161 --- /dev/null +++ b/OpenSim/Framework/BasicDOSProtector.cs @@ -0,0 +1,209 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using System; +using System.Collections.Generic; +using System.Reflection; +using log4net; + +namespace OpenSim.Framework +{ + + public class BasicDOSProtector + { + public enum ThrottleAction + { + DoThrottledMethod, + DoThrow + } + private readonly CircularBuffer _generalRequestTimes; // General request checker + private readonly BasicDosProtectorOptions _options; + private readonly Dictionary> _deeperInspection; // per client request checker + private readonly Dictionary _tempBlocked; // blocked list + private readonly System.Timers.Timer _forgetTimer; // Cleanup timer + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private readonly System.Threading.ReaderWriterLockSlim _lockSlim = new System.Threading.ReaderWriterLockSlim(); + public BasicDOSProtector(BasicDosProtectorOptions options) + { + _generalRequestTimes = new CircularBuffer(options.MaxRequestsInTimeframe + 1, true); + _generalRequestTimes.Put(0); + _options = options; + _deeperInspection = new Dictionary>(); + _tempBlocked = new Dictionary(); + _forgetTimer = new System.Timers.Timer(); + _forgetTimer.Elapsed += delegate + { + _forgetTimer.Enabled = false; + + List removes = new List(); + _lockSlim.EnterReadLock(); + foreach (string str in _tempBlocked.Keys) + { + if ( + Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), + _tempBlocked[str]) > 0) + removes.Add(str); + } + _lockSlim.ExitReadLock(); + lock (_deeperInspection) + { + _lockSlim.EnterWriteLock(); + for (int i = 0; i < removes.Count; i++) + { + _tempBlocked.Remove(removes[i]); + _deeperInspection.Remove(removes[i]); + } + _lockSlim.ExitWriteLock(); + } + foreach (string str in removes) + { + m_log.InfoFormat("[{0}] client: {1} is no longer blocked.", + _options.ReportingName, str); + } + _lockSlim.EnterReadLock(); + if (_tempBlocked.Count > 0) + _forgetTimer.Enabled = true; + _lockSlim.ExitReadLock(); + }; + + _forgetTimer.Interval = _options.ForgetTimeSpan.TotalMilliseconds; + } + + /// + /// Given a string Key, Returns if that context is blocked + /// + /// A Key identifying the context + /// bool Yes or No, True or False for blocked + public bool IsBlocked(string key) + { + bool ret = false; + _lockSlim.EnterReadLock(); + ret = _tempBlocked.ContainsKey(key); + _lockSlim.ExitReadLock(); + return ret; + } + + /// + /// Process the velocity of this context + /// + /// + /// + /// + public bool Process(string key, string endpoint) + { + if (_options.MaxRequestsInTimeframe < 1 || _options.RequestTimeSpan.TotalMilliseconds < 1) + return true; + + string clientstring = key; + + _lockSlim.EnterReadLock(); + if (_tempBlocked.ContainsKey(clientstring)) + { + _lockSlim.ExitReadLock(); + + if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod) + return false; + else + throw new System.Security.SecurityException("Throttled"); + } + _lockSlim.ExitReadLock(); + + _generalRequestTimes.Put(Util.EnvironmentTickCount()); + + if (_generalRequestTimes.Size == _generalRequestTimes.Capacity && + (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _generalRequestTimes.Get()) < + _options.RequestTimeSpan.TotalMilliseconds)) + { + //Trigger deeper inspection + if (DeeperInspection(key, endpoint)) + return true; + if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod) + return false; + else + throw new System.Security.SecurityException("Throttled"); + } + return true; + } + + /// + /// At this point, the rate limiting code needs to track 'per user' velocity. + /// + /// Context Key, string representing a rate limiting context + /// + /// + private bool DeeperInspection(string key, string endpoint) + { + lock (_deeperInspection) + { + string clientstring = key; + + + if (_deeperInspection.ContainsKey(clientstring)) + { + _deeperInspection[clientstring].Put(Util.EnvironmentTickCount()); + if (_deeperInspection[clientstring].Size == _deeperInspection[clientstring].Capacity && + (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _deeperInspection[clientstring].Get()) < + _options.RequestTimeSpan.TotalMilliseconds)) + { + //Looks like we're over the limit + _lockSlim.EnterWriteLock(); + if (!_tempBlocked.ContainsKey(clientstring)) + _tempBlocked.Add(clientstring, Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds); + else + _tempBlocked[clientstring] = Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds; + _lockSlim.ExitWriteLock(); + + 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); + + return false; + } + //else + // return true; + } + else + { + _deeperInspection.Add(clientstring, new CircularBuffer(_options.MaxRequestsInTimeframe + 1, true)); + _deeperInspection[clientstring].Put(Util.EnvironmentTickCount()); + _forgetTimer.Enabled = true; + } + + } + return true; + } + + } + + + public class BasicDosProtectorOptions + { + public int MaxRequestsInTimeframe; + public TimeSpan RequestTimeSpan; + public TimeSpan ForgetTimeSpan; + public bool AllowXForwardedFor; + public string ReportingName = "BASICDOSPROTECTOR"; + public BasicDOSProtector.ThrottleAction ThrottledAction = BasicDOSProtector.ThrottleAction.DoThrottledMethod; + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/BasicDOSProtector.cs b/OpenSim/Framework/Servers/HttpServer/BasicDOSProtector.cs deleted file mode 100644 index 50a4ea2..0000000 --- a/OpenSim/Framework/Servers/HttpServer/BasicDOSProtector.cs +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -using System; -using System.Collections.Generic; -using System.Reflection; -using log4net; - -namespace OpenSim.Framework.Servers.HttpServer -{ - - public class BasicDOSProtector - { - public enum ThrottleAction - { - DoThrottledMethod, - DoThrow - } - private readonly CircularBuffer _generalRequestTimes; // General request checker - private readonly BasicDosProtectorOptions _options; - private readonly Dictionary> _deeperInspection; // per client request checker - private readonly Dictionary _tempBlocked; // blocked list - private readonly System.Timers.Timer _forgetTimer; // Cleanup timer - private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - private readonly System.Threading.ReaderWriterLockSlim _lockSlim = new System.Threading.ReaderWriterLockSlim(); - public BasicDOSProtector(BasicDosProtectorOptions options) - { - _generalRequestTimes = new CircularBuffer(options.MaxRequestsInTimeframe + 1, true); - _generalRequestTimes.Put(0); - _options = options; - _deeperInspection = new Dictionary>(); - _tempBlocked = new Dictionary(); - _forgetTimer = new System.Timers.Timer(); - _forgetTimer.Elapsed += delegate - { - _forgetTimer.Enabled = false; - - List removes = new List(); - _lockSlim.EnterReadLock(); - foreach (string str in _tempBlocked.Keys) - { - if ( - Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), - _tempBlocked[str]) > 0) - removes.Add(str); - } - _lockSlim.ExitReadLock(); - lock (_deeperInspection) - { - _lockSlim.EnterWriteLock(); - for (int i = 0; i < removes.Count; i++) - { - _tempBlocked.Remove(removes[i]); - _deeperInspection.Remove(removes[i]); - } - _lockSlim.ExitWriteLock(); - } - foreach (string str in removes) - { - m_log.InfoFormat("[{0}] client: {1} is no longer blocked.", - _options.ReportingName, str); - } - _lockSlim.EnterReadLock(); - if (_tempBlocked.Count > 0) - _forgetTimer.Enabled = true; - _lockSlim.ExitReadLock(); - }; - - _forgetTimer.Interval = _options.ForgetTimeSpan.TotalMilliseconds; - } - public bool Process(string key, string endpoint) - { - if (_options.MaxRequestsInTimeframe < 1 || _options.RequestTimeSpan.TotalMilliseconds < 1) - return true; - - string clientstring = key; - - _lockSlim.EnterReadLock(); - if (_tempBlocked.ContainsKey(clientstring)) - { - _lockSlim.ExitReadLock(); - - if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod) - return false; - else - throw new System.Security.SecurityException("Throttled"); - } - _lockSlim.ExitReadLock(); - - _generalRequestTimes.Put(Util.EnvironmentTickCount()); - - if (_generalRequestTimes.Size == _generalRequestTimes.Capacity && - (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _generalRequestTimes.Get()) < - _options.RequestTimeSpan.TotalMilliseconds)) - { - //Trigger deeper inspection - if (DeeperInspection(key, endpoint)) - return true; - if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod) - return false; - else - throw new System.Security.SecurityException("Throttled"); - } - return true; - } - private bool DeeperInspection(string key, string endpoint) - { - lock (_deeperInspection) - { - string clientstring = key; - - - if (_deeperInspection.ContainsKey(clientstring)) - { - _deeperInspection[clientstring].Put(Util.EnvironmentTickCount()); - if (_deeperInspection[clientstring].Size == _deeperInspection[clientstring].Capacity && - (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _deeperInspection[clientstring].Get()) < - _options.RequestTimeSpan.TotalMilliseconds)) - { - //Looks like we're over the limit - _lockSlim.EnterWriteLock(); - if (!_tempBlocked.ContainsKey(clientstring)) - _tempBlocked.Add(clientstring, Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds); - else - _tempBlocked[clientstring] = Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds; - _lockSlim.ExitWriteLock(); - - 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); - - return false; - } - //else - // return true; - } - else - { - _deeperInspection.Add(clientstring, new CircularBuffer(_options.MaxRequestsInTimeframe + 1, true)); - _deeperInspection[clientstring].Put(Util.EnvironmentTickCount()); - _forgetTimer.Enabled = true; - } - - } - return true; - } - - } - - - public class BasicDosProtectorOptions - { - public int MaxRequestsInTimeframe; - public TimeSpan RequestTimeSpan; - public TimeSpan ForgetTimeSpan; - public bool AllowXForwardedFor; - public string ReportingName = "BASICDOSPROTECTOR"; - public BasicDOSProtector.ThrottleAction ThrottledAction = BasicDOSProtector.ThrottleAction.DoThrottledMethod; - } -} -- cgit v1.1 From 75f63ecfcd885ae71bca130ee0db1ecc997b86cd Mon Sep 17 00:00:00 2001 From: teravus Date: Wed, 9 Oct 2013 22:21:25 -0500 Subject: * Add a session concurrency option per key. Allows developer/config to specify number of concurrent requests on a service. --- OpenSim/Framework/BasicDOSProtector.cs | 96 ++++++++++++++++++---- .../BaseStreamHandlerBasicDOSProtector.cs | 8 +- .../HttpServer/GenericHTTPBasicDOSProtector.cs | 14 +++- .../Servers/HttpServer/XmlRpcBasicDOSProtector.cs | 7 +- 4 files changed, 102 insertions(+), 23 deletions(-) (limited to 'OpenSim/Framework') diff --git a/OpenSim/Framework/BasicDOSProtector.cs b/OpenSim/Framework/BasicDOSProtector.cs index b470161..89bfa94 100644 --- a/OpenSim/Framework/BasicDOSProtector.cs +++ b/OpenSim/Framework/BasicDOSProtector.cs @@ -43,9 +43,11 @@ namespace OpenSim.Framework private readonly BasicDosProtectorOptions _options; private readonly Dictionary> _deeperInspection; // per client request checker private readonly Dictionary _tempBlocked; // blocked list + private readonly Dictionary _sessions; private readonly System.Timers.Timer _forgetTimer; // Cleanup timer private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - private readonly System.Threading.ReaderWriterLockSlim _lockSlim = new System.Threading.ReaderWriterLockSlim(); + private readonly System.Threading.ReaderWriterLockSlim _blockLockSlim = new System.Threading.ReaderWriterLockSlim(); + private readonly System.Threading.ReaderWriterLockSlim _sessionLockSlim = new System.Threading.ReaderWriterLockSlim(); public BasicDOSProtector(BasicDosProtectorOptions options) { _generalRequestTimes = new CircularBuffer(options.MaxRequestsInTimeframe + 1, true); @@ -53,13 +55,14 @@ namespace OpenSim.Framework _options = options; _deeperInspection = new Dictionary>(); _tempBlocked = new Dictionary(); + _sessions = new Dictionary(); _forgetTimer = new System.Timers.Timer(); _forgetTimer.Elapsed += delegate { _forgetTimer.Enabled = false; List removes = new List(); - _lockSlim.EnterReadLock(); + _blockLockSlim.EnterReadLock(); foreach (string str in _tempBlocked.Keys) { if ( @@ -67,26 +70,27 @@ namespace OpenSim.Framework _tempBlocked[str]) > 0) removes.Add(str); } - _lockSlim.ExitReadLock(); + _blockLockSlim.ExitReadLock(); lock (_deeperInspection) { - _lockSlim.EnterWriteLock(); + _blockLockSlim.EnterWriteLock(); for (int i = 0; i < removes.Count; i++) { _tempBlocked.Remove(removes[i]); _deeperInspection.Remove(removes[i]); + _sessions.Remove(removes[i]); } - _lockSlim.ExitWriteLock(); + _blockLockSlim.ExitWriteLock(); } foreach (string str in removes) { m_log.InfoFormat("[{0}] client: {1} is no longer blocked.", _options.ReportingName, str); } - _lockSlim.EnterReadLock(); + _blockLockSlim.EnterReadLock(); if (_tempBlocked.Count > 0) _forgetTimer.Enabled = true; - _lockSlim.ExitReadLock(); + _blockLockSlim.ExitReadLock(); }; _forgetTimer.Interval = _options.ForgetTimeSpan.TotalMilliseconds; @@ -100,9 +104,9 @@ namespace OpenSim.Framework public bool IsBlocked(string key) { bool ret = false; - _lockSlim.EnterReadLock(); + _blockLockSlim.EnterReadLock(); ret = _tempBlocked.ContainsKey(key); - _lockSlim.ExitReadLock(); + _blockLockSlim.ExitReadLock(); return ret; } @@ -119,20 +123,58 @@ namespace OpenSim.Framework string clientstring = key; - _lockSlim.EnterReadLock(); + _blockLockSlim.EnterReadLock(); if (_tempBlocked.ContainsKey(clientstring)) { - _lockSlim.ExitReadLock(); + _blockLockSlim.ExitReadLock(); if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod) return false; else throw new System.Security.SecurityException("Throttled"); } - _lockSlim.ExitReadLock(); + + _blockLockSlim.ExitReadLock(); - _generalRequestTimes.Put(Util.EnvironmentTickCount()); + lock (_generalRequestTimes) + _generalRequestTimes.Put(Util.EnvironmentTickCount()); + if (_options.MaxConcurrentSessions > 0) + { + int sessionscount = 0; + + _sessionLockSlim.EnterReadLock(); + if (_sessions.ContainsKey(key)) + sessionscount = _sessions[key]; + _sessionLockSlim.ExitReadLock(); + + if (sessionscount > _options.MaxConcurrentSessions) + { + // Add to blocking and cleanup methods + lock (_deeperInspection) + { + _blockLockSlim.EnterWriteLock(); + if (!_tempBlocked.ContainsKey(clientstring)) + { + _tempBlocked.Add(clientstring, + Util.EnvironmentTickCount() + + (int) _options.ForgetTimeSpan.TotalMilliseconds); + _forgetTimer.Enabled = true; + 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); + + } + else + _tempBlocked[clientstring] = Util.EnvironmentTickCount() + + (int) _options.ForgetTimeSpan.TotalMilliseconds; + _blockLockSlim.ExitWriteLock(); + + } + + + } + else + ProcessConcurrency(key, endpoint); + } if (_generalRequestTimes.Size == _generalRequestTimes.Capacity && (Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(), _generalRequestTimes.Get()) < _options.RequestTimeSpan.TotalMilliseconds)) @@ -147,6 +189,29 @@ namespace OpenSim.Framework } return true; } + private void ProcessConcurrency(string key, string endpoint) + { + _sessionLockSlim.EnterWriteLock(); + if (_sessions.ContainsKey(key)) + _sessions[key] = _sessions[key] + 1; + else + _sessions.Add(key,1); + _sessionLockSlim.ExitWriteLock(); + } + public void ProcessEnd(string key, string endpoint) + { + _sessionLockSlim.EnterWriteLock(); + if (_sessions.ContainsKey(key)) + { + _sessions[key]--; + if (_sessions[key] <= 0) + _sessions.Remove(key); + } + else + _sessions.Add(key, 1); + + _sessionLockSlim.ExitWriteLock(); + } /// /// At this point, the rate limiting code needs to track 'per user' velocity. @@ -169,12 +234,12 @@ namespace OpenSim.Framework _options.RequestTimeSpan.TotalMilliseconds)) { //Looks like we're over the limit - _lockSlim.EnterWriteLock(); + _blockLockSlim.EnterWriteLock(); if (!_tempBlocked.ContainsKey(clientstring)) _tempBlocked.Add(clientstring, Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds); else _tempBlocked[clientstring] = Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds; - _lockSlim.ExitWriteLock(); + _blockLockSlim.ExitWriteLock(); 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); @@ -205,5 +270,6 @@ namespace OpenSim.Framework public bool AllowXForwardedFor; public string ReportingName = "BASICDOSPROTECTOR"; public BasicDOSProtector.ThrottleAction ThrottledAction = BasicDOSProtector.ThrottleAction.DoThrottledMethod; + public int MaxConcurrentSessions; } } diff --git a/OpenSim/Framework/Servers/HttpServer/BaseStreamHandlerBasicDOSProtector.cs b/OpenSim/Framework/Servers/HttpServer/BaseStreamHandlerBasicDOSProtector.cs index 9b8b8c2..1b88545 100644 --- a/OpenSim/Framework/Servers/HttpServer/BaseStreamHandlerBasicDOSProtector.cs +++ b/OpenSim/Framework/Servers/HttpServer/BaseStreamHandlerBasicDOSProtector.cs @@ -55,12 +55,14 @@ namespace OpenSim.Framework.Servers.HttpServer { byte[] result; RequestsReceived++; - - if (_dosProtector.Process(GetClientString(httpRequest), GetRemoteAddr(httpRequest))) + string clientstring = GetClientString(httpRequest); + string endpoint = GetRemoteAddr(httpRequest); + if (_dosProtector.Process(clientstring, endpoint)) result = ProcessRequest(path, request, httpRequest, httpResponse); else result = ThrottledRequest(path, request, httpRequest, httpResponse); - + if (_options.MaxConcurrentSessions > 0) + _dosProtector.ProcessEnd(clientstring, endpoint); RequestsHandled++; diff --git a/OpenSim/Framework/Servers/HttpServer/GenericHTTPBasicDOSProtector.cs b/OpenSim/Framework/Servers/HttpServer/GenericHTTPBasicDOSProtector.cs index 39c98b4..cd4b8ff 100644 --- a/OpenSim/Framework/Servers/HttpServer/GenericHTTPBasicDOSProtector.cs +++ b/OpenSim/Framework/Servers/HttpServer/GenericHTTPBasicDOSProtector.cs @@ -47,10 +47,18 @@ namespace OpenSim.Framework.Servers.HttpServer } public Hashtable Process(Hashtable request) { - if (_dosProtector.Process(GetClientString(request), GetRemoteAddr(request))) - return _normalMethod(request); + Hashtable process = null; + string clientstring= GetClientString(request); + string endpoint = GetRemoteAddr(request); + if (_dosProtector.Process(clientstring, endpoint)) + process = _normalMethod(request); else - return _throttledMethod(request); + process = _throttledMethod(request); + + if (_options.MaxConcurrentSessions>0) + _dosProtector.ProcessEnd(clientstring, endpoint); + + return process; } private string GetRemoteAddr(Hashtable request) diff --git a/OpenSim/Framework/Servers/HttpServer/XmlRpcBasicDOSProtector.cs b/OpenSim/Framework/Servers/HttpServer/XmlRpcBasicDOSProtector.cs index bc7efb6..f212208 100644 --- a/OpenSim/Framework/Servers/HttpServer/XmlRpcBasicDOSProtector.cs +++ b/OpenSim/Framework/Servers/HttpServer/XmlRpcBasicDOSProtector.cs @@ -53,11 +53,14 @@ namespace OpenSim.Framework.Servers.HttpServer { XmlRpcResponse resp = null; - if (_dosProtector.Process(GetClientString(request, client), GetEndPoint(request, client))) + string clientstring = GetClientString(request, client); + string endpoint = GetEndPoint(request, client); + if (_dosProtector.Process(clientstring, endpoint)) resp = _normalMethod(request, client); else resp = _throttledMethod(request, client); - + if (_options.MaxConcurrentSessions > 0) + _dosProtector.ProcessEnd(clientstring, endpoint); return resp; } -- cgit v1.1