* 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.
link-sitting
teravus 2013-10-07 21:35:55 -05:00
parent 31246ecd04
commit f76cc6036e
12 changed files with 1112 additions and 7 deletions

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
}
}

View File

@ -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
{
/// <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 CircularBuffer<int> _generalRequestTimes;
private readonly BasicDosProtectorOptions _options;
private readonly Dictionary<string, CircularBuffer<int>> _deeperInspection;
private readonly Dictionary<string, int> _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<int>(options.MaxRequestsInTimeframe + 1, true);
_generalRequestTimes.Put(0);
_options = options;
_deeperInspection = new Dictionary<string, CircularBuffer<int>>();
_tempBlocked = new Dictionary<string, int>();
_forgetTimer = new System.Timers.Timer();
_forgetTimer.Elapsed += delegate
{
_forgetTimer.Enabled = false;
List<string> removes = new List<string>();
_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<int>(_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;
}
}
}

View File

@ -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<int> _generalRequestTimes;
private readonly BasicDosProtectorOptions _options;
private readonly Dictionary<string, CircularBuffer<int>> _deeperInspection;
private readonly Dictionary<string, int> _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<int>(options.MaxRequestsInTimeframe + 1, true);
_generalRequestTimes.Put(0);
_options = options;
_deeperInspection = new Dictionary<string, CircularBuffer<int>>();
_tempBlocked = new Dictionary<string, int>();
_forgetTimer = new System.Timers.Timer();
_forgetTimer.Elapsed += delegate
{
_forgetTimer.Enabled = false;
List<string> removes = new List<string>();
_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<int>(_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;
}
}
}

View File

@ -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<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 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<int>(options.MaxRequestsInTimeframe + 1,true);
_generalRequestTimes.Put(0);
_options = options;
_deeperInspection = new Dictionary<string, CircularBuffer<int>>();
_tempBlocked = new Dictionary<string, int>();
_forgetTimer = new System.Timers.Timer();
_forgetTimer.Elapsed += delegate
{
_forgetTimer.Enabled = false;
List<string> removes = new List<string>();
_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<int>(_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;
}
}

View File

@ -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 = 5,
ReportingName = "FRIENDSDOSPROTECTOR",
RequestTimeSpan = TimeSpan.FromSeconds(5),
ThrottledAction = ThrottleAction.DoThrottledMethod
})
{
m_FriendsModule = fmodule;
}

View File

@ -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 = 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"] = "I blocked you! HAHAHAHAHAHAHHAHAH";
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");

View File

@ -42,14 +42,22 @@ using OpenSim.Framework.Servers.HttpServer;
namespace OpenSim.Server.Handlers.Asset
{
public class AssetServerGetHandler : BaseStreamHandler
public class AssetServerGetHandler : BaseStreamHandlerBasicDOSProtector
{
// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private IAssetService m_AssetService;
public AssetServerGetHandler(IAssetService service) :
base("GET", "/assets")
base("GET", "/assets",new BasicDosProtectorOptions()
{
AllowXForwardedFor = true,
ForgetTimeSpan = TimeSpan.FromSeconds(2),
MaxRequestsInTimeframe = 5,
ReportingName = "ASSETGETDOSPROTECTOR",
RequestTimeSpan = TimeSpan.FromSeconds(5),
ThrottledAction = ThrottleAction.DoThrottledMethod
})
{
m_AssetService = service;
}

View File

@ -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)
{

View File

@ -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);

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.

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

View File

@ -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