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. --- .../BaseStreamHandlerBasicDOSProtector.cs | 233 ++++++++++++++++++++ .../HttpServer/GenericHTTPBasicDOSProtector.cs | 238 +++++++++++++++++++++ .../Servers/HttpServer/XmlRpcBasicDOSProtector.cs | 211 ++++++++++++++++++ 3 files changed, 682 insertions(+) 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/Servers') 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/Servers') 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. --- .../Servers/HttpServer/BasicDOSProtector.cs | 181 --------------------- 1 file changed, 181 deletions(-) delete mode 100644 OpenSim/Framework/Servers/HttpServer/BasicDOSProtector.cs (limited to 'OpenSim/Framework/Servers') 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. --- .../HttpServer/BaseStreamHandlerBasicDOSProtector.cs | 8 +++++--- .../Servers/HttpServer/GenericHTTPBasicDOSProtector.cs | 14 +++++++++++--- .../Servers/HttpServer/XmlRpcBasicDOSProtector.cs | 7 +++++-- 3 files changed, 21 insertions(+), 8 deletions(-) (limited to 'OpenSim/Framework/Servers') 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