Commit 4733cd60 authored by John Selbie's avatar John Selbie

DDOS support with --ddp command line parameter

parent de61025d
# BOOST_INCLUDE := -I/home/jselbie/boost_1_51_0
# BOOST_INCLUDE := -I/home/jselbie/boost_1_57_0
# OPENSSL_INCLUDE := -I/home/jselbie/lib/openssl
DEFINES := -DNDEBUG
......
include ../common.inc
PROJECT_TARGET := libnetworkutils.a
PROJECT_OBJS := adapters.o polling.o recvfromex.o resolvehostname.o stunsocket.o
PROJECT_OBJS := adapters.o polling.o ratelimiter.o recvfromex.o resolvehostname.o stunsocket.o
INCLUDES := $(BOOST_INCLUDE) -I../common -I../stuncore
......
#include "commonincludes.hpp"
#include "socketaddress.h"
#include "fasthash.h"
#include "ratelimiter.h"
RateLimiter::RateLimiter(size_t tablesize, bool isUsingLock)
{
_table.InitTable(tablesize, tablesize/2);
this->_isUsingLock = isUsingLock;
pthread_mutex_init(&_mutex, NULL);
}
RateLimiter::~RateLimiter()
{
pthread_mutex_destroy(&_mutex);
}
time_t RateLimiter::get_time()
{
return time(NULL);
}
uint64_t RateLimiter::get_rate(const RateTracker* pRT)
{
if (pRT->count < MIN_COUNT_FOR_CONSIDERATION)
{
return 0;
}
time_t seconds = 1;
if (pRT->lastEntryTime > pRT->firstEntryTime)
{
seconds = pRT->lastEntryTime - pRT->firstEntryTime;
}
uint64_t rate = (pRT->count * 3600) / seconds;
return rate;
}
bool RateLimiter::RateCheck(const CSocketAddress& addr)
{
if (_isUsingLock)
{
pthread_mutex_lock(&_mutex);
}
bool result = RateCheckImpl(addr);
if (_isUsingLock)
{
pthread_mutex_unlock(&_mutex);
}
return result;
}
bool RateLimiter::RateCheckImpl(const CSocketAddress& addr)
{
RateTrackerAddress rtaddr;
addr.GetIP(rtaddr.addrbytes, sizeof(rtaddr.addrbytes));
time_t currentTime = get_time();
RateTracker* pRT = this->_table.Lookup(rtaddr);
uint64_t rate = 0;
// handle these cases differently:
// 1. Not in the table
// Insert into table
// if table is full, reset the table and re-insert
// return true
// 2. In the penalty box
// Check for parole eligibility
// otherwise, return false
// 3. Not in the penalty box.
// Remove from table if his previous record is sufficiently old (and return true)
//
if (pRT == NULL)
{
RateTracker rt;
rt.count = 1;
rt.firstEntryTime = currentTime;
rt.lastEntryTime = rt.firstEntryTime;
rt.penaltyTime = 0;
int result = _table.Insert(rtaddr, rt);
if (result == -1)
{
// the table is full - try again after dumping the table
// I've considered a half dozen alternatives to doing this, but this is the simplest
_table.Reset();
_table.Insert(rtaddr, rt);
}
return true;
}
pRT->count++;
pRT->lastEntryTime = currentTime;
rate = get_rate(pRT);
if (pRT->penaltyTime != 0)
{
if (pRT->penaltyTime >= currentTime)
{
return false;
}
if (rate < MAX_RATE)
{
pRT->penaltyTime = 0;
// not reseting firstEntryTime or lastEntryTime.
return true;
}
// he's out of penalty, but is still flooding us. Tack on another hour
pRT->penaltyTime = currentTime + PENALTY_TIME_SECONDS;
return false;
}
if (rate >= MAX_RATE)
{
pRT->penaltyTime = currentTime + PENALTY_TIME_SECONDS; // welcome to the penalty box
return false;
}
if ((pRT->lastEntryTime - pRT->firstEntryTime) > RESET_INTERVAL_SECONDS)
{
// he's been a good citizen this whole time, we can take him out of the table
// to save room for another entry
_table.Remove(rtaddr);
}
return true;
}
#ifndef RATE_TRACKER_H
#define RATE_TRACKER_H
#include "socketaddress.h"
#include "fasthash.h"
struct RateTracker
{
uint64_t count; // how many packets since firstEntryTime
time_t firstEntryTime; // what time the first entry came in at
time_t lastEntryTime; // what time the last entry came in at (may not be needed
time_t penaltyTime; // what time he's allowed out of penalty (0 if no penalty)
};
struct RateTrackerAddress
{
uint64_t addrbytes[2];
bool operator==(const RateTrackerAddress& other)
{
return ((other.addrbytes[0] == addrbytes[0]) && (other.addrbytes[1] == addrbytes[1]));
}
};
inline size_t FastHash_Hash(const RateTrackerAddress& addr)
{
size_t result;
if (sizeof(size_t) >= 8)
{
result = addr.addrbytes[0] ^ addr.addrbytes[1];
}
else
{
uint32_t* pTmp = (uint32_t*)(addr.addrbytes);
result = pTmp[0] ^ pTmp[1] ^ pTmp[2] ^ pTmp[3];
}
return result;
}
class RateLimiter
{
protected:
virtual time_t get_time();
uint64_t get_rate(const RateTracker* pRT);
FastHashDynamic<RateTrackerAddress, RateTracker> _table;
bool _isUsingLock;
pthread_mutex_t _mutex;
bool RateCheckImpl(const CSocketAddress& addr);
public:
static const uint64_t MAX_RATE = 3600; // 60/minute normalized to an hourly rate
static const uint64_t MIN_COUNT_FOR_CONSIDERATION = 60;
static const time_t RESET_INTERVAL_SECONDS = 120; // if he ever exceeds the hourly rate within a two minute interval, he gets penalized
static const time_t PENALTY_TIME_SECONDS = 3600; // one hour penalty
bool RateCheck(const CSocketAddress& addr);
RateLimiter(size_t tablesize, bool isUsingLock);
virtual ~RateLimiter();
};
#endif
.TH STUNCLIENT 1 "" "January 22, 2012" "User Manual"
.TH "STUNCLIENT" "1" "" "January 22, 2012" "User Manual"
.SH NAME
.PP
stunclient - command line app for the STUN protocol
stunclient \- command line app for the STUN protocol
.SH SYNOPSIS
.PP
\f[B]stunclient\f[] [OPTIONS] server [port]
\f[B]stunclient\f[] OPTIONS (#options) server [port]
.SH DESCRIPTION
.PP
stunclient attempts to discover the local host\[aq]s own external IP
......@@ -17,13 +17,13 @@ The following options are supported.
.IP
.nf
\f[C]
--mode\ MODE
--localaddr\ INTERFACE
--localport\ PORTNUMBER
--family\ IPVERSION
--protocol\ PROTO
--verbosity\ LOGLEVEL
--help
\-\-mode\ MODE
\-\-localaddr\ INTERFACE
\-\-localport\ PORTNUMBER
\-\-family\ IPVERSION
\-\-protocol\ PROTO
\-\-verbosity\ LOGLEVEL
\-\-help
\f[]
.fi
.PP
......@@ -45,7 +45,7 @@ The default is 3478 for UDP and TCP.
.PP
* * * * *
.PP
\f[B]--mode\f[] MODE
\f[B]\-\-mode\f[] MODE
.PP
Where MODE is either "basic" or "full".
"basic" mode is the default and indicates that the client should perform
......@@ -56,7 +56,7 @@ The NAT filtering test is only supported for UDP.
.PP
* * * * *
.PP
\f[B]--localaddr\f[] INTERFACE or IPADDRESS
\f[B]\-\-localaddr\f[] INTERFACE or IPADDRESS
.PP
The value for this option may the name of an interface (such as "eth0"
or "lo").
......@@ -69,7 +69,7 @@ listen for responses on all addresses (INADDR_ANY).
.PP
* * * * *
.PP
\f[B]--localport\f[] PORTNUM
\f[B]\-\-localport\f[] PORTNUM
.PP
PORTNUM is a value between 1 to 65535.
This is the UDP or TCP port that the primary and alternate interfaces
......@@ -79,21 +79,21 @@ used.
.PP
* * * * *
.PP
\f[B]--family\f[] IPVERSION
\f[B]\-\-family\f[] IPVERSION
.PP
IPVERSION is either "4" or "6" to specify the usage of IPV4 or IPV6.
If not specified, the default value is "4".
.PP
* * * * *
.PP
\f[B]--protocol\f[] PROTO
\f[B]\-\-protocol\f[] PROTO
.PP
PROTO is either "udp" or "tcp".
"udp" is the default if this parameter is not specified
.PP
* * * * *
.PP
\f[B]--verbosity\f[] LOGLEVEL
\f[B]\-\-verbosity\f[] LOGLEVEL
.PP
Sets the verbosity of the logging level.
0 is the default (minimal output and logging).
......@@ -102,7 +102,7 @@ Sets the verbosity of the logging level.
.PP
* * * * *
.PP
\f[B]--help\f[] Prints this help page
\f[B]\-\-help\f[] Prints this help page
.SH EXAMPLES
.TP
.B stunclient stunserver.org 3478
......@@ -111,13 +111,13 @@ Performs a simple binding test request with the server listening at
.RS
.RE
.TP
.B stunclient --mode full --localport 9999 12.34.56.78
.B stunclient \-\-mode full \-\-localport 9999 12.34.56.78
Performs a full set of UDP NAT behavior tests from local port 9999 to
the server listening at IP Address 12.34.56.78 (port 3478)
.RS
.RE
.TP
.B stunclient --protocol tcp stun.selbie.com
.B stunclient \-\-protocol tcp stun.selbie.com
Performs a simple binding test using TCP to server listening on the
default port of 3478 at stun.selbie.com
.RS
......
This diff is collapsed.
.TH STUNSERVER 1 "" "January 22, 2012" "User Manual"
.TH "STUNSERVER" "1" "" "January 22, 2012" "User Manual"
.SH NAME
.PP
stunserver - STUN protocol service (RFCs: 3489, 5389, 5789, 5780)
stunserver \- STUN protocol service (RFCs: 3489, 5389, 5789, 5780)
.SH SYNOPSIS
.PP
\f[B]stunserver\f[] [OPTIONS]
\f[B]stunserver\f[] OPTIONS (#options)
.SH DESCRIPTION
.PP
stunserver starts a STUN listening service that responds to STUN binding
......@@ -16,31 +16,31 @@ The following options are supported.
.IP
.nf
\f[C]
--mode\ MODE
--primaryinterface\ INTERFACE
--altinterface\ INTERFACE
--primaryport\ PORTNUMBER
--altport\ PORTNUMBER
--family\ IPVERSION
--protocol\ PROTO
--maxconn\ MAXCONN
--verbosity\ LOGLEVEL
--primaryadvertised
--altadvertised
--help
\-\-mode\ MODE
\-\-primaryinterface\ INTERFACE
\-\-altinterface\ INTERFACE
\-\-primaryport\ PORTNUMBER
\-\-altport\ PORTNUMBER
\-\-family\ IPVERSION
\-\-protocol\ PROTO
\-\-maxconn\ MAXCONN
\-\-verbosity\ LOGLEVEL
\-\-primaryadvertised
\-\-altadvertised
\-\-help
\f[]
.fi
.PP
Details of each option are as follows.
.PP
\f[B]--mode\f[] MODE
\f[B]\-\-mode\f[] MODE
.PP
Where the MODE parameter specified is either "basic" or "full".
In basic mode, the server listens on a single port.
Basic mode is sufficient for basic NAT traversal scenarios in which a
client needs to discover its external IP address and obtain a port
mapping for a local port it is listening on.
The STUN CHANGE-REQUEST attribute is not supported in basic mode.
The STUN CHANGE\-REQUEST attribute is not supported in basic mode.
.PP
In full mode, the STUN service listens on two different interfaces and
two different ports on each.
......@@ -49,14 +49,14 @@ the response back from one of the alternate interfaces and/or ports.
Full mode facilitates clients attempting to discover NAT behavior and
NAT filtering behavior of the network they are on.
Full mode requires two unique IP addresses on the host.
When run over TCP, the service is not able to support a CHANGE-REQUEST
When run over TCP, the service is not able to support a CHANGE\-REQUEST
attribute from the client.
.PP
If this parameter is not specified, basic mode is the default.
.PP
* * * * *
.PP
\f[B]--primaryinterface\f[] INTERFACE
\f[B]\-\-primaryinterface\f[] INTERFACE
.PP
Where INTERFACE specified is either a local IP address (e.g.
"192.168.1.2") of the host or the name of a network interface (e.g.
......@@ -67,27 +67,27 @@ primary listening address.
.PP
In basic mode, the default is to bind to all available adapters
(INADDR_ANY).
In full mode, the default is to bind to the first non-localhost adapter
In full mode, the default is to bind to the first non\-localhost adapter
with a configured IP address.
.PP
* * * * *
.PP
\f[B]--altinterface\f[] INTERFACE
\f[B]\-\-altinterface\f[] INTERFACE
.PP
Where INTERFACE specified is either a local IP address (e.g.
"192.168.1.3") of the host or the name of a network interface (e.g.
"eth1").
.PP
This parameter is nearly identical as the --primaryinterface option
This parameter is nearly identical as the \-\-primaryinterface option
except that it specifies the alternate listening address for full mode.
.PP
This option is ignored in basic mode.
In full mode, the default is to bind to the second non-localhost adapter
with a configured IP address.
In full mode, the default is to bind to the second non\-localhost
adapter with a configured IP address.
.PP
* * * * *
.PP
\f[B]--primaryport\f[] PORTNUM
\f[B]\-\-primaryport\f[] PORTNUM
.PP
Where PORTNUM is a value between 1 to 65535.
.PP
......@@ -100,7 +100,7 @@ The default is 3478.
.PP
* * * * *
.PP
\f[B]--altport\f[] PORTNUM
\f[B]\-\-altport\f[] PORTNUM
.PP
Where PORTNUM is a value between 1 to 65535.
.PP
......@@ -114,7 +114,7 @@ The default is 3479.
.PP
* * * * *
.PP
\f[B]--family\f[] IPVERSION
\f[B]\-\-family\f[] IPVERSION
.PP
Where IPVERSION is either "4" or "6" to specify the usage of IPV4 or
IPV6.
......@@ -123,7 +123,7 @@ The default family is 4 for IPv4 usage.
.PP
* * * * *
.PP
\f[B]--protocol\f[] PROTO
\f[B]\-\-protocol\f[] PROTO
.PP
Where PROTO is either IP protocol, "udp" or "tcp".
.PP
......@@ -131,7 +131,7 @@ udp is the default.
.PP
* * * * *
.PP
\f[B]--maxconn\f[] MAXCONN
\f[B]\-\-maxconn\f[] MAXCONN
.PP
Where MAXCONN is a value between 1 and 100000.
.PP
......@@ -143,7 +143,7 @@ The default value is 1000
.PP
* * * * *
.PP
\f[B]--verbosity\f[] LOGLEVEL
\f[B]\-\-verbosity\f[] LOGLEVEL
.PP
Where LOGLEVEL is a value greater than or equal to 0.
.PP
......@@ -158,13 +158,23 @@ The default is 0.
.PP
* * * * *
.PP
\f[B]--primaryadvertised\f[] PRIMARY-IP
\f[B]\-\-ddp\f[]
.PP
The \-\-ddp switch is for "Distributed Denial (of service) Protection".
Any client IP address that floods the service with too many packets in a
short interval is put into a "penalty box" that will result in
subsequent packets received from this IP to be dropped.
The result is that the client receives no response.
.PP
* * * * *
.PP
\f[B]\-\-primaryadvertised\f[] PRIMARY\-IP
.PP
\f[B]--altadvertised\f[] ALT-IP
\f[B]\-\-altadvertised\f[] ALT\-IP
.PP
Where PRIMARY-IP and ALT-IP are valid numeric IP address strings (e.g.
Where PRIMARY\-IP and ALT\-IP are valid numeric IP address strings (e.g.
"101.23.45.67") that are the public IP addresses of the
--primaryinterface and --altinterface addresses discussed above.
\-\-primaryinterface and \-\-altinterface addresses discussed above.
.PP
These two parameters are for advanced usage only.
It is intended for support of running a STUN server in full mode on
......@@ -174,24 +184,24 @@ Do not set this parameter unless you know specifically the effect it
creates.
.PP
Normally, without these parameters being set, the ORIGIN attribute,
OTHER-ADDRESS attribute, and CHANGED-ADDRESS attributes are are
OTHER\-ADDRESS attribute, and CHANGED\-ADDRESS attributes are are
determined by querying the local adapters or sockets for the IP address
they are listening on.
When running the server in a NAT environment, binding responses will
still contain a correct set of mapping address attributes, such that P2P
connectivity may succeed.
However, the the ORIGIN, OTHER-ADDRESS, and CHANGED-ADDRESS attributes
However, the the ORIGIN, OTHER\-ADDRESS, and CHANGED\-ADDRESS attributes
sent by the server will be incorrect.
The impact of sending an incorrect OTHER-ADDRESS or CHANGED-ADDRESS will
result in a client attempting to do NAT Behavior tests or NAT filtering
tests to report an incorrect result.
The impact of sending an incorrect OTHER\-ADDRESS or CHANGED\-ADDRESS
will result in a client attempting to do NAT Behavior tests or NAT
filtering tests to report an incorrect result.
.PP
For more details, visit www.stunprotocol.org for details on how to
correctly set these parameters for use within Amazon EC2.
.PP
* * * * *
.PP
\f[B]--help\f[]
\f[B]\-\-help\f[]
.PP
Prints this help page
.SH EXAMPLES
......@@ -201,9 +211,8 @@ With no options, starts a basic STUN binding service on UDP port 3478.
.RS
.RE
.TP
.B stunserver --mode full --primaryinterface 128.34.56.78 --altinterface
128.34.56.79
Above example starts a dual-host STUN service on the the interfaces
.B stunserver \-\-mode full \-\-primaryinterface 128.34.56.78 \-\-altinterface 128.34.56.79
Above example starts a dual\-host STUN service on the the interfaces
identified by the IP address "128.34.56.78" and "128.34.56.79".
There are four UDP socket listeners
.RS
......@@ -216,7 +225,7 @@ IP, Alternate Port) 128.34.56.79:3478 (Primary IP, Primary Port)
An error occurs if the addresses specified do not exist on the local
host running the service.
.TP
.B stunserver --mode full --primaryinterface eth0 --altinterface eth1
.B stunserver \-\-mode full \-\-primaryinterface eth0 \-\-altinterface eth1
Same as above, except the interfaces are specified by their names as
enumerated by the system.
(The "ifconfig" or "ipconfig" command will enumerate available interface
......
......@@ -150,6 +150,15 @@ The default is 0.
____
**--ddp**
The --ddp switch is for "Distributed Denial (of service) Protection". Any client IP address that
floods the service with too many packets in a short interval is put into a "penalty box" that
will result in subsequent packets received from this IP to be dropped. The result is that
the client receives no response.
____
**--primaryadvertised** PRIMARY-IP
**--altadvertised** ALT-IP
......
This diff is collapsed.
......@@ -112,6 +112,7 @@ struct StartupArgs
std::string strHelp;
std::string strVerbosity;
std::string strMaxConnections;
std::string strDosProtect;
};
......@@ -132,6 +133,7 @@ void DumpStartupArgs(StartupArgs& args)
PRINTARG(strHelp);
PRINTARG(strVerbosity);
PRINTARG(strMaxConnections);
PRINTARG(strDosProtect);
Logging::LogMsg(LL_DEBUG, "--------------------------\n");
}
......@@ -487,7 +489,9 @@ HRESULT BuildServerConfigurationFromArgs(StartupArgs& argsIn, CStunServerConfig*
Chk(hr);
}
}
// ---- DDOS PROTECTION SWITCH -------------------------------------------
config.fEnableDosProtection = (argsIn.strDosProtect.length() > 0);
*pConfigOut = config;
hr = S_OK;
......@@ -515,6 +519,7 @@ HRESULT ParseCommandLineArgs(int argc, char** argv, int startindex, StartupArgs*
cmdline.AddOption("maxconn", required_argument, &pStartupArgs->strMaxConnections);
cmdline.AddOption("help", no_argument, &pStartupArgs->strHelp);
cmdline.AddOption("verbosity", required_argument, &pStartupArgs->strVerbosity);
cmdline.AddOption("ddp", no_argument, &pStartupArgs->strDosProtect);
cmdline.ParseCommandLine(argc, argv, startindex, &fError);
......
......@@ -21,6 +21,7 @@
#include "stunsocket.h"
#include "stunsocketthread.h"
#include "server.h"
#include "ratelimiter.h"
......@@ -31,7 +32,8 @@ fHasAP(false),
fHasAA(false),
fMultiThreadedMode(false),
fTCP(false),
nMaxConnections(0) // zero means default
nMaxConnections(0), // zero means default
fEnableDosProtection(false)
{
;
}
......@@ -97,6 +99,7 @@ HRESULT CStunServer::Initialize(const CStunServerConfig& config)
int socketcount = 0;
CRefCountedPtr<IStunAuth> _spAuth;
TransportAddressSet tsa = {};
boost::shared_ptr<RateLimiter> spLimiter;
// cleanup any thing that's going on now
Shutdown();
......@@ -132,17 +135,24 @@ HRESULT CStunServer::Initialize(const CStunServerConfig& config)
ChkIf(socketcount == 0, E_INVALIDARG);
if (config.fEnableDosProtection)
{
Logging::LogMsg(LL_DEBUG, "Creating rate limiter for ddos protection\n");
// hard coding to 25000 ip addresses
spLimiter = boost::shared_ptr<RateLimiter>(new RateLimiter(25000, config.fMultiThreadedMode));
}
if (config.fMultiThreadedMode == false)
{
Logging::LogMsg(LL_DEBUG, "Configuring single threaded mode\n");
// create one thread for all the sockets
CStunSocketThread* pThread = new CStunSocketThread();
ChkIf(pThread==NULL, E_OUTOFMEMORY);
_threads.push_back(pThread);
Chk(pThread->Init(_arrSockets, &tsa, _spAuth, (SocketRole)-1));
Chk(pThread->Init(_arrSockets, &tsa, _spAuth, (SocketRole)-1, spLimiter));
}
else
{
......@@ -159,7 +169,7 @@ HRESULT CStunServer::Initialize(const CStunServerConfig& config)
pThread = new CStunSocketThread();
ChkIf(pThread==NULL, E_OUTOFMEMORY);
_threads.push_back(pThread);
Chk(pThread->Init(_arrSockets, &tsa, _spAuth, rolePrimaryRecv));
Chk(pThread->Init(_arrSockets, &tsa, _spAuth, rolePrimaryRecv, spLimiter));
}
}
}
......
......@@ -47,15 +47,13 @@ public:
CSocketAddress addrPrimaryAdvertised; // public-IP for PP and PA (port is ignored)
CSocketAddress addrAlternateAdvertised; // public-IP for AP and AA (port is ignored)
bool fEnableDosProtection; // enable denial of service protection
CStunServerConfig();
};
class CStunServer :
public CBasicRefCount,
public CObjectFactory<CStunServer>,
......@@ -74,7 +72,7 @@ private:
CRefCountedPtr<IStunAuth> _spAuth;
HRESULT AddSocket(TransportAddressSet* pTSA, SocketRole role, const CSocketAddress& addrListen, const CSocketAddress& addrAdvertise);
public:
HRESULT Initialize(const CStunServerConfig& config);
......
......@@ -21,6 +21,7 @@
#include "stunsocket.h"
#include "stunsocketthread.h"
#include "recvfromex.h"
#include "ratelimiter.h"
CStunSocketThread::CStunSocketThread() :
......@@ -46,7 +47,7 @@ void CStunSocketThread::ClearSocketArray()
_socks.clear();
}
HRESULT CStunSocketThread::Init(CStunSocket* arrayOfFourSockets, TransportAddressSet* pTSA, IStunAuth* pAuth, SocketRole rolePrimaryRecv)
HRESULT CStunSocketThread::Init(CStunSocket* arrayOfFourSockets, TransportAddressSet* pTSA, IStunAuth* pAuth, SocketRole rolePrimaryRecv, boost::shared_ptr<RateLimiter>& spLimiter)
{
HRESULT hr = S_OK;
......@@ -94,6 +95,8 @@ HRESULT CStunSocketThread::Init(CStunSocket* arrayOfFourSockets, TransportAddres
_rotation = 0;
_spAuth.Attach(pAuth);
_spLimiter = spLimiter;
Cleanup:
return hr;
......@@ -206,7 +209,7 @@ HRESULT CStunSocketThread::WaitForStopAndClose()
ClearSocketArray();
UninitThreadBuffers();
return S_OK;
}
......@@ -276,6 +279,10 @@ void CStunSocketThread::Run()
int recvflags = fMultiSocketMode ? MSG_DONTWAIT : 0;
CStunSocket* pSocket = _socks[0];
int ret;
char szIPRemote[100] = {};
char szIPLocal[100] = {};
bool allowed_to_pass = true;
int sendsocketcount = 0;
......@@ -319,17 +326,29 @@ void CStunSocketThread::Run()
{
_msgIn.addrLocal.SetPort(pSocket->GetLocalAddress().GetPort());
}
if (Logging::GetLogLevel() >= LL_VERBOSE)
{
char szIPRemote[100];
char szIPLocal[100];
_msgIn.addrRemote.ToStringBuffer(szIPRemote, 100);
_msgIn.addrLocal.ToStringBuffer(szIPLocal, 100);
Logging::LogMsg(LL_VERBOSE, "recvfrom returns %d from %s on local interface %s", ret, szIPRemote, szIPLocal);
}
}
else
{
szIPRemote[0] = '\0';
szIPLocal[0] = '\0';
}
Logging::LogMsg(LL_VERBOSE, "recvfrom returns %d from %s on local interface %s", ret, szIPRemote, szIPLocal);
allowed_to_pass = (_spLimiter.get() != NULL) ? _spLimiter->RateCheck(_msgIn.addrRemote) : true;
if (allowed_to_pass == false)
{
Logging::LogMsg(LL_VERBOSE, "RateLimiter signals false for packet from %s", szIPRemote);
}
if (ret < 0)
if ((ret < 0) || (allowed_to_pass == false))
{
// error
continue;
......
......@@ -20,6 +20,7 @@
#define STUNSOCKETTHREAD_H
#include "stunsocket.h"
#include "ratelimiter.h"
class CStunServer;
......@@ -32,7 +33,7 @@ public:
CStunSocketThread();
~CStunSocketThread();
HRESULT Init(CStunSocket* arrayOfFourSockets, TransportAddressSet* pTSA, IStunAuth* pAuth, SocketRole rolePrimaryRecv);
HRESULT Init(CStunSocket* arrayOfFourSockets, TransportAddressSet* pTSA, IStunAuth* pAuth, SocketRole rolePrimaryRecv, boost::shared_ptr<RateLimiter>& _spRateLimiter);
HRESULT Start();
HRESULT SignalForStop(bool fPostMessages);
......@@ -70,6 +71,8 @@ private:
StunMessageIn _msgIn;
StunMessageOut _msgOut;
boost::shared_ptr<RateLimiter> _spLimiter;
HRESULT InitThreadBuffers();
void UninitThreadBuffers();
......
......@@ -227,7 +227,7 @@ CStunSocket* CTCPStunThread::GetListenSocket(int sock)
HRESULT CTCPStunThread::Init(const TransportAddressSet& tsaListen, const TransportAddressSet& tsaHandler, IStunAuth* pAuth, int maxConnections)
HRESULT CTCPStunThread::Init(const TransportAddressSet& tsaListen, const TransportAddressSet& tsaHandler, IStunAuth* pAuth, int maxConnections, boost::shared_ptr<RateLimiter>& spLimiter)
{
HRESULT hr = S_OK;
int ret;
......@@ -282,6 +282,8 @@ HRESULT CTCPStunThread::Init(const TransportAddressSet& tsaListen, const Transpo
_pNewConnList = &_hashConnections1;
_pOldConnList = &_hashConnections2;
_spLimiter = spLimiter;
_fNeedToExit = false;
Cleanup:
......@@ -480,13 +482,31 @@ StunConnection* CTCPStunThread::AcceptConnection(CStunSocket* pListenSocket)
ASSERT(::IsValidSocketRole(role));
socktmp = ::accept(listensock, (sockaddr*)&addrClient, &socklen);
err = errno;
Logging::LogMsg(LL_VERBOSE, "accept returns %d (errno == %d)", socktmp, (socktmp<0)?err:0);
ChkIfA(socktmp == -1, E_FAIL);
// --- rate limit check-------
if (_spLimiter.get())
{
CSocketAddress addr = CSocketAddress(addrClient);
bool allowed_to_pass = _spLimiter->RateCheck(addr);
if (allowed_to_pass == false)
{
if (Logging::GetLogLevel() >= LL_VERBOSE)
{
char szIP[100];
addr.ToStringBuffer(szIP, 100);
Logging::LogMsg(LL_VERBOSE, "Rate Limiter has blocked incoming connection from IP %s", szIP);
}
ChkIf(false, E_FAIL); // this will trigger the socket to be immediately closed
}
}
// --------------------------
clientsock = socktmp;
pConn = _connectionpool.GetConnection(clientsock, role);
......@@ -854,6 +874,7 @@ HRESULT CTCPServer::Initialize(const CStunServerConfig& config)
HRESULT hr = S_OK;
TransportAddressSet tsaListenAll;
TransportAddressSet tsaHandler;
boost::shared_ptr<RateLimiter> spLimiter;
ChkIfA(_threads[0] != NULL, E_UNEXPECTED); // we can't already be initialized, right?
......@@ -874,11 +895,16 @@ HRESULT CTCPServer::Initialize(const CStunServerConfig& config)
InitTSA(&tsaListenAll, RoleAP, config.fHasAP, config.addrAP, CSocketAddress());
InitTSA(&tsaListenAll, RoleAA, config.fHasAA, config.addrAA, CSocketAddress());
if (config.fEnableDosProtection)
{
spLimiter = boost::shared_ptr<RateLimiter>(new RateLimiter(20000, config.fMultiThreadedMode));
}
if (config.fMultiThreadedMode == false)
{
_threads[0] = new CTCPStunThread();
ChkA(_threads[0]->Init(tsaListenAll, tsaHandler, _spAuth, config.nMaxConnections));
ChkA(_threads[0]->Init(tsaListenAll, tsaHandler, _spAuth, config.nMaxConnections, spLimiter));
}
else
{
......@@ -902,7 +928,7 @@ HRESULT CTCPServer::Initialize(const CStunServerConfig& config)
_threads[threadindex] = new CTCPStunThread();
Chk(_threads[threadindex]->Init(tsaListen, tsaHandler, _spAuth, config.nMaxConnections));
Chk(_threads[threadindex]->Init(tsaListen, tsaHandler, _spAuth, config.nMaxConnections, spLimiter));
}
}
}
......
......@@ -25,6 +25,7 @@
#include "messagehandler.h"
#include "stunconnection.h"
#include "polling.h"
#include "ratelimiter.h"
......@@ -41,6 +42,8 @@ class CTCPStunThread
CRefCountedPtr<IPolling> _spPolling;
bool _fListenSocketsOnEpoll;
boost::shared_ptr<RateLimiter> _spLimiter;
// epoll helpers
HRESULT SetListenSocketsOnEpoll(bool fEnable);
......@@ -112,7 +115,7 @@ public:
// tsaListen are the set of addresses we listen to connections on (either 1 address or 4 addresses)
// tsaHandler is what gets passed to the CStunRequestHandler for formation of the "other-address" attribute
HRESULT Init(const TransportAddressSet& tsaListen, const TransportAddressSet& tsaHandler, IStunAuth* pAuth, int maxConnections);
HRESULT Init(const TransportAddressSet& tsaListen, const TransportAddressSet& tsaHandler, IStunAuth* pAuth, int maxConnections, boost::shared_ptr<RateLimiter>& spLimiter);
HRESULT Start();
HRESULT Stop();
};
......
include ../common.inc
PROJECT_TARGET := stuntestcode
PROJECT_OBJS := testatomichelpers.o testbuilder.o testclientlogic.o testcmdline.o testcode.o testdatastream.o testfasthash.o testintegrity.o testmessagehandler.o testpolling.o testreader.o testrecvfromex.o
PROJECT_OBJS := testatomichelpers.o testbuilder.o testclientlogic.o testcmdline.o testcode.o testdatastream.o testfasthash.o testintegrity.o testmessagehandler.o testpolling.o testratelimiter.o testreader.o testrecvfromex.o
INCLUDES := $(BOOST_INCLUDE) $(OPENSSL_INCLUDE) -I../common -I../stuncore -I../networkutils
LIB_PATH := -L../networkutils -L../stuncore -L../common
......
......@@ -33,6 +33,7 @@
#include "polling.h"
#include "testpolling.h"
#include "testatomichelpers.h"
#include "testratelimiter.h"
void ReaderFuzzTest()
{
......@@ -84,6 +85,7 @@ void RunUnitTests()
boost::shared_ptr<CTestFastHash> spTestFastHash(new CTestFastHash);
boost::shared_ptr<CTestPolling> spTestPolling(new CTestPolling);
boost::shared_ptr<CTestAtomicHelpers> spTestAtomicHelpers(new CTestAtomicHelpers);
boost::shared_ptr<CTestRateLimiter> spTestRateLimiter(new CTestRateLimiter);
vecTests.push_back(spTestDataStream.get());
vecTests.push_back(spTestReader.get());
......@@ -97,6 +99,7 @@ void RunUnitTests()
vecTests.push_back(spTestFastHash.get());
vecTests.push_back(spTestPolling.get());
vecTests.push_back(spTestAtomicHelpers.get());
vecTests.push_back(spTestRateLimiter.get());
for (size_t index = 0; index < vecTests.size(); index++)
......
#include "commonincludes.hpp"
#include "unittest.h"
#include "ratelimiter.h"
#include "testratelimiter.h"
class RateLimiterMockTime : public RateLimiter
{
public:
time_t _time;
RateLimiterMockTime(size_t tablesize) : RateLimiter(tablesize, false), _time(0)
{
}
void set_time(time_t t)
{
_time = t;
}
virtual time_t get_time()
{
return _time;
}
};
HRESULT CTestRateLimiter::Run()
{
HRESULT hr = S_OK;
hr = Test1();
if (SUCCEEDED(hr))
{
hr = Test2();
}
return hr;
}
HRESULT CTestRateLimiter::Test1()
{
// simulate 60 packets within one minute
CSocketAddress sockaddr(0x12341234,1234);
CSocketAddress sockaddr_goodguy(0x67896789,6789);
RateLimiterMockTime ratelimiter(20000);
HRESULT hr = S_OK;
bool result = false;
for (int x = 0; x < 59; x++)
{
ratelimiter.set_time(x / 2);
result = ratelimiter.RateCheck(sockaddr);
ChkIf(result == false, E_FAIL);
if ((x % 4) == 0)
{
result = ratelimiter.RateCheck(sockaddr_goodguy);
ChkIf(result == false, E_FAIL);
}
}
// 60th packet should fail for bad guy
result = ratelimiter.RateCheck(sockaddr);
ChkIf(result, E_FAIL);
// should not impact good guy
result = ratelimiter.RateCheck(sockaddr_goodguy);
ChkIf(result == false, E_FAIL);
// at the one hour mark, he should still be in the penalty box
ratelimiter.set_time(RateLimiter::PENALTY_TIME_SECONDS);
result = ratelimiter.RateCheck(sockaddr);
ChkIf(result, E_FAIL);
// but at the 30 second mark he should be out
ratelimiter.set_time(RateLimiter::PENALTY_TIME_SECONDS+31);
result = ratelimiter.RateCheck(sockaddr);
ChkIf(result==false, E_FAIL);
Cleanup:
return hr;
}
HRESULT CTestRateLimiter::Test2()
{
// simulate 20000 ip addresses getting in
RateLimiterMockTime ratelimiter(20000);
CSocketAddress badguy_addr(10000, 9999);
CSocketAddress goodguy_addr(40000, 9999);
bool result;
HRESULT hr = S_OK;
uint32_t ip = 0;
for (int x = 0; x < 20000; x++)
{
ip = (uint32_t)x;
CSocketAddress addr(ip, 9999);
result = ratelimiter.RateCheck(addr);
ChkIf(result == false, E_FAIL);
}
// force an entry in the table to be in the penalty boxy
for (int x = 0; x < 60; x++)
{
ratelimiter.RateCheck(badguy_addr);
}
result = ratelimiter.RateCheck(badguy_addr);
ChkIf(result, E_FAIL);
// force another entry that should flush the table
result = ratelimiter.RateCheck(goodguy_addr);
ChkIf(result==false, E_FAIL);
// bad guy will no longer be in table
result = ratelimiter.RateCheck(badguy_addr);
ChkIf(result==false, E_FAIL);
Cleanup:
return hr;
}
#include "commonincludes.hpp"
#include "unittest.h"
#include "ratelimiter.h"
class CTestRateLimiter : public IUnitTest
{
public:
HRESULT Run();
HRESULT Test1();
HRESULT Test2();
UT_DECLARE_TEST_NAME("CTestRateLimiter");
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment