Compare commits

...

5 Commits

Author SHA1 Message Date
teravus 75f63ecfcd * Add a session concurrency option per key. Allows developer/config to specify number of concurrent requests on a service. 2013-10-09 22:21:25 -05:00
teravus e7ea053c4a * Remove a test *cleanup* 2013-10-07 23:52:44 -05:00
teravus 1df58d04b1 * 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.
2013-10-07 23:48:24 -05:00
teravus 75fdd6054d * Refactor
* Break out common BasicDOSProtector code into separate class.
2013-10-07 23:19:50 -05:00
teravus f76cc6036e * Added a Basic DOS protection container/base object for the most common HTTP Server handlers. XMLRPC Handler, GenericHttpHandler and <Various>StreamHandler
* Applied the XmlRpcBasicDOSProtector.cs to the login service as both an example, and good practice.
* Applied the BaseStreamHandlerBasicDOSProtector.cs to the friends service as an example of the DOS Protector on StreamHandlers
* Added CircularBuffer, used for CPU and Memory friendly rate monitoring.
* DosProtector has 2 states, 1. Just Check for blocked users and check general velocity, 2. Track velocity per user,     It only jumps to 2 if it's getting a lot of requests, and state 1 is about as resource friendly as if it wasn't even there.
2013-10-07 21:35:55 -05:00
12 changed files with 1012 additions and 5 deletions
+275
View File
@@ -0,0 +1,275 @@
/*
* 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<int> _generalRequestTimes; // General request checker
private readonly BasicDosProtectorOptions _options;
private readonly Dictionary<string, CircularBuffer<int>> _deeperInspection; // per client request checker
private readonly Dictionary<string, int> _tempBlocked; // blocked list
private readonly Dictionary<string, int> _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 _blockLockSlim = new System.Threading.ReaderWriterLockSlim();
private readonly System.Threading.ReaderWriterLockSlim _sessionLockSlim = new System.Threading.ReaderWriterLockSlim();
public BasicDOSProtector(BasicDosProtectorOptions options)
{
_generalRequestTimes = new CircularBuffer<int>(options.MaxRequestsInTimeframe + 1, true);
_generalRequestTimes.Put(0);
_options = options;
_deeperInspection = new Dictionary<string, CircularBuffer<int>>();
_tempBlocked = new Dictionary<string, int>();
_sessions = new Dictionary<string, int>();
_forgetTimer = new System.Timers.Timer();
_forgetTimer.Elapsed += delegate
{
_forgetTimer.Enabled = false;
List<string> removes = new List<string>();
_blockLockSlim.EnterReadLock();
foreach (string str in _tempBlocked.Keys)
{
if (
Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(),
_tempBlocked[str]) > 0)
removes.Add(str);
}
_blockLockSlim.ExitReadLock();
lock (_deeperInspection)
{
_blockLockSlim.EnterWriteLock();
for (int i = 0; i < removes.Count; i++)
{
_tempBlocked.Remove(removes[i]);
_deeperInspection.Remove(removes[i]);
_sessions.Remove(removes[i]);
}
_blockLockSlim.ExitWriteLock();
}
foreach (string str in removes)
{
m_log.InfoFormat("[{0}] client: {1} is no longer blocked.",
_options.ReportingName, str);
}
_blockLockSlim.EnterReadLock();
if (_tempBlocked.Count > 0)
_forgetTimer.Enabled = true;
_blockLockSlim.ExitReadLock();
};
_forgetTimer.Interval = _options.ForgetTimeSpan.TotalMilliseconds;
}
/// <summary>
/// Given a string Key, Returns if that context is blocked
/// </summary>
/// <param name="key">A Key identifying the context</param>
/// <returns>bool Yes or No, True or False for blocked</returns>
public bool IsBlocked(string key)
{
bool ret = false;
_blockLockSlim.EnterReadLock();
ret = _tempBlocked.ContainsKey(key);
_blockLockSlim.ExitReadLock();
return ret;
}
/// <summary>
/// Process the velocity of this context
/// </summary>
/// <param name="key"></param>
/// <param name="endpoint"></param>
/// <returns></returns>
public bool Process(string key, string endpoint)
{
if (_options.MaxRequestsInTimeframe < 1 || _options.RequestTimeSpan.TotalMilliseconds < 1)
return true;
string clientstring = key;
_blockLockSlim.EnterReadLock();
if (_tempBlocked.ContainsKey(clientstring))
{
_blockLockSlim.ExitReadLock();
if (_options.ThrottledAction == ThrottleAction.DoThrottledMethod)
return false;
else
throw new System.Security.SecurityException("Throttled");
}
_blockLockSlim.ExitReadLock();
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))
{
//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 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();
}
/// <summary>
/// At this point, the rate limiting code needs to track 'per user' velocity.
/// </summary>
/// <param name="key">Context Key, string representing a rate limiting context</param>
/// <param name="endpoint"></param>
/// <returns></returns>
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
_blockLockSlim.EnterWriteLock();
if (!_tempBlocked.ContainsKey(clientstring))
_tempBlocked.Add(clientstring, Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds);
else
_tempBlocked[clientstring] = Util.EnvironmentTickCount() + (int)_options.ForgetTimeSpan.TotalMilliseconds;
_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);
return false;
}
//else
// return true;
}
else
{
_deeperInspection.Add(clientstring, new CircularBuffer<int>(_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;
public int MaxConcurrentSessions;
}
}
+312
View File
@@ -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<T> : ICollection<T>, IEnumerable<T>, 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<T>.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<T> 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<T> Members
int ICollection<T>.Count
{
get { return Size; }
}
bool ICollection<T>.IsReadOnly
{
get { return false; }
}
void ICollection<T>.Add(T item)
{
Put(item);
}
bool ICollection<T>.Remove(T item)
{
if (size == 0)
return false;
Get();
return true;
}
#endregion
#region IEnumerable<T> Members
IEnumerator<T> IEnumerable<T>.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
}
}
@@ -0,0 +1,107 @@
/*
* 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.IO;
namespace OpenSim.Framework.Servers.HttpServer
{
/// <summary>
/// BaseStreamHandlerBasicDOSProtector Base streamed request handler.
/// </summary>
/// <remarks>
/// Inheriting classes should override ProcessRequest() rather than Handle()
/// </remarks>
public abstract class BaseStreamHandlerBasicDOSProtector : BaseRequestHandler, IStreamedRequestHandler
{
private readonly BasicDosProtectorOptions _options;
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)
{
_options = options;
_dosProtector = new BasicDOSProtector(_options);
}
public virtual byte[] Handle(
string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
{
byte[] result;
RequestsReceived++;
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++;
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 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;
}
}
}
@@ -0,0 +1,119 @@
/*
* 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.Collections;
namespace OpenSim.Framework.Servers.HttpServer
{
public class GenericHTTPDOSProtector
{
private readonly GenericHTTPMethod _normalMethod;
private readonly GenericHTTPMethod _throttledMethod;
private readonly BasicDosProtectorOptions _options;
private readonly BasicDOSProtector _dosProtector;
public GenericHTTPDOSProtector(GenericHTTPMethod normalMethod, GenericHTTPMethod throttledMethod, BasicDosProtectorOptions options)
{
_normalMethod = normalMethod;
_throttledMethod = throttledMethod;
_options = options;
_dosProtector = new BasicDOSProtector(_options);
}
public Hashtable Process(Hashtable request)
{
Hashtable process = null;
string clientstring= GetClientString(request);
string endpoint = GetRemoteAddr(request);
if (_dosProtector.Process(clientstring, endpoint))
process = _normalMethod(request);
else
process = _throttledMethod(request);
if (_options.MaxConcurrentSessions>0)
_dosProtector.ProcessEnd(clientstring, endpoint);
return process;
}
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;
}
}
}
@@ -0,0 +1,91 @@
/*
* 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.Net;
using Nwc.XmlRpc;
using OpenSim.Framework;
namespace OpenSim.Framework.Servers.HttpServer
{
public class XmlRpcBasicDOSProtector
{
private readonly XmlRpcMethod _normalMethod;
private readonly XmlRpcMethod _throttledMethod;
private readonly BasicDosProtectorOptions _options;
private readonly BasicDOSProtector _dosProtector;
public XmlRpcBasicDOSProtector(XmlRpcMethod normalMethod, XmlRpcMethod throttledMethod,BasicDosProtectorOptions options)
{
_normalMethod = normalMethod;
_throttledMethod = throttledMethod;
_options = options;
_dosProtector = new BasicDOSProtector(_options);
}
public XmlRpcResponse Process(XmlRpcRequest request, IPEndPoint client)
{
XmlRpcResponse resp = null;
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;
}
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;
}
private string GetEndPoint(XmlRpcRequest request, IPEndPoint client)
{
return client.Address.ToString();
}
}
}
@@ -42,14 +42,22 @@ using log4net;
namespace OpenSim.Region.CoreModules.Avatar.Friends
{
public class FriendsRequestHandler : BaseStreamHandler
public class FriendsRequestHandler : BaseStreamHandlerBasicDOSProtector
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private FriendsModule m_FriendsModule;
public FriendsRequestHandler(FriendsModule fmodule)
: base("POST", "/friends")
: base("POST", "/friends", new BasicDosProtectorOptions()
{
AllowXForwardedFor = true,
ForgetTimeSpan = TimeSpan.FromMinutes(2),
MaxRequestsInTimeframe = 20,
ReportingName = "FRIENDSDOSPROTECTOR",
RequestTimeSpan = TimeSpan.FromSeconds(5),
ThrottledAction = BasicDOSProtector.ThrottleAction.DoThrottledMethod
})
{
m_FriendsModule = fmodule;
}
@@ -165,7 +165,16 @@ namespace OpenSim.Region.CoreModules.World.WorldMap
regionimage = regionimage.Replace("-", "");
m_log.Info("[WORLD MAP]: JPEG Map location: " + m_scene.RegionInfo.ServerURI + "index.php?method=" + regionimage);
MainServer.Instance.AddHTTPHandler(regionimage, OnHTTPGetMapImage);
MainServer.Instance.AddHTTPHandler(regionimage,
new GenericHTTPDOSProtector(OnHTTPGetMapImage, OnHTTPThrottled, new BasicDosProtectorOptions()
{
AllowXForwardedFor = false,
ForgetTimeSpan = TimeSpan.FromMinutes(2),
MaxRequestsInTimeframe = 4,
ReportingName = "MAPDOSPROTECTOR",
RequestTimeSpan = TimeSpan.FromSeconds(10),
ThrottledAction = BasicDOSProtector.ThrottleAction.DoThrottledMethod
}).Process);
MainServer.Instance.AddLLSDHandler(
"/MAP/MapItems/" + m_scene.RegionInfo.RegionHandle.ToString(), HandleRemoteMapItemRequest);
@@ -1081,6 +1090,16 @@ namespace OpenSim.Region.CoreModules.World.WorldMap
block.Y = (ushort)(r.RegionLocY / Constants.RegionSize);
}
public Hashtable OnHTTPThrottled(Hashtable keysvals)
{
Hashtable reply = new Hashtable();
int statuscode = 500;
reply["str_response_string"] = "";
reply["int_response_code"] = statuscode;
reply["content_type"] = "text/plain";
return reply;
}
public Hashtable OnHTTPGetMapImage(Hashtable keysvals)
{
m_log.Debug("[WORLD MAP]: Sending map image jpeg");
@@ -145,6 +145,17 @@ namespace OpenSim.Server.Handlers.Login
return FailedXMLRPCResponse();
}
public XmlRpcResponse HandleXMLRPCLoginBlocked(XmlRpcRequest request, IPEndPoint client)
{
XmlRpcResponse response = new XmlRpcResponse();
Hashtable resp = new Hashtable();
resp["reason"] = "presence";
resp["message"] = "Logins are currently restricted. Please try again later.";
resp["login"] = "false";
response.Value = resp;
return response;
}
public XmlRpcResponse HandleXMLRPCSetLoginLevel(XmlRpcRequest request, IPEndPoint remoteClient)
{
@@ -44,6 +44,7 @@ namespace OpenSim.Server.Handlers.Login
private ILoginService m_LoginService;
private bool m_Proxy;
private BasicDosProtectorOptions m_DosProtectionOptions;
public LLLoginServiceInConnector(IConfigSource config, IHttpServer server, IScene scene) :
base(config, server, String.Empty)
@@ -88,6 +89,16 @@ namespace OpenSim.Server.Handlers.Login
throw new Exception(String.Format("No LocalServiceModule for LoginService in config file"));
m_Proxy = serverConfig.GetBoolean("HasProxy", false);
m_DosProtectionOptions = new BasicDosProtectorOptions();
// Dos Protection Options
m_DosProtectionOptions.AllowXForwardedFor = serverConfig.GetBoolean("DOSAllowXForwardedForHeader", false);
m_DosProtectionOptions.RequestTimeSpan =
TimeSpan.FromMilliseconds(serverConfig.GetInt("DOSRequestTimeFrameMS", 10000));
m_DosProtectionOptions.MaxRequestsInTimeframe = serverConfig.GetInt("DOSMaxRequestsInTimeFrame", 5);
m_DosProtectionOptions.ForgetTimeSpan =
TimeSpan.FromMilliseconds(serverConfig.GetInt("DOSForgiveClientAfterMS", 120000));
m_DosProtectionOptions.ReportingName = "LOGINDOSPROTECTION";
return loginService;
}
@@ -95,7 +106,9 @@ namespace OpenSim.Server.Handlers.Login
private void InitializeHandlers(IHttpServer server)
{
LLLoginHandlers loginHandlers = new LLLoginHandlers(m_LoginService, m_Proxy);
server.AddXmlRPCHandler("login_to_simulator", loginHandlers.HandleXMLRPCLogin, false);
server.AddXmlRPCHandler("login_to_simulator",
new XmlRpcBasicDOSProtector(loginHandlers.HandleXMLRPCLogin,loginHandlers.HandleXMLRPCLoginBlocked,
m_DosProtectionOptions).Process, false);
server.AddXmlRPCHandler("set_login_level", loginHandlers.HandleXMLRPCSetLoginLevel, false);
server.SetDefaultLLSDHandler(loginHandlers.HandleLLSDLogin);
server.AddWebSocketHandler("/WebSocket/GridLogin", loginHandlers.HandleWebSocketLoginEvents);
+16
View File
@@ -0,0 +1,16 @@
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.
+19
View File
@@ -356,6 +356,25 @@ MapGetServiceConnector = "8002/OpenSim.Server.Handlers.dll:MapGetServiceConnecto
;; 'America/Los_Angeles' is used on Linux/Mac systems whilst 'Pacific Standard Time' is used on Windows
DSTZone = "America/Los_Angeles;Pacific Standard Time"
;Basic Login Service Dos Protection Tweaks
;;
;; Some Grids/Users use a transparent proxy that makes use of the X-Forwarded-For HTTP Header, If you do, set this to true
;; If you set this to true and you don't have a transparent proxy, it may allow attackers to put random things in the X-Forwarded-For header to
;; get around this basic DOS protection.
;DOSAllowXForwardedForHeader = false
;;
;; The protector adds up requests during this rolling period of time, default 10 seconds
;DOSRequestTimeFrameMS = 10000
;;
;; The amount of requests in the above timeframe from the same endpoint that triggers protection
;DOSMaxRequestsInTimeFrame = 5
;;
;; The amount of time that a specific endpoint is blocked. Default 2 minutes.
;DOSForgiveClientAfterMS = 120000
;;
;; To turn off basic dos protection, set the DOSMaxRequestsInTimeFrame to 0.
[MapImageService]
LocalServiceModule = "OpenSim.Services.MapImageService.dll:MapImageService"
; Set this if you want to change the default
@@ -117,7 +117,7 @@
SRV_AssetServerURI = "http://127.0.0.1:9000"
SRV_ProfileServerURI = "http://127.0.0.1:9000"
SRV_FriendsServerURI = "http://127.0.0.1:9000"
SRV_IMServerURI = "http://127.0.0.1:9000"
SRV_IMServerURI = "http://127.0.0.1:9000
;; For Viewer 2
MapTileURL = "http://127.0.0.1:9000/"
@@ -150,6 +150,23 @@
;AllowedClients = ""
;DeniedClients = ""
; Basic Login Service Dos Protection Tweaks
; ;
; ; Some Grids/Users use a transparent proxy that makes use of the X-Forwarded-For HTTP Header, If you do, set this to true
; ; If you set this to true and you don't have a transparent proxy, it may allow attackers to put random things in the X-Forwarded-For header to
; ; get around this basic DOS protection.
; DOSAllowXForwardedForHeader = false
; ;
; ; The protector adds up requests during this rolling period of time, default 10 seconds
; DOSRequestTimeFrameMS = 10000
; ;
; ; The amount of requests in the above timeframe from the same endpoint that triggers protection
; DOSMaxRequestsInTimeFrame = 5
; ;
; ; The amount of time that a specific endpoint is blocked. Default 2 minutes.
; DOSForgiveClientAfterMS = 120000
; ;
; ; To turn off basic dos protection, set the DOSMaxRequestsInTimeFrame to 0.
[FreeswitchService]
;; If FreeSWITCH is not being used then you don't need to set any of these parameters