MXS-2414: Prototype connection attempt throttling
The RateLimit class stores authentication failure data mapped by the client IP addresses. The authentication failures are limited per thread. The limits are still hard-coded and at least the number of failures should be made configurable. The simplest, most maintainable and acceptably efficient implementation for DDoS protection is a thread-local unordered_map. The unwanted side-effect of "scaling" of the number of allowed authentication failures is unlikely to be problematic in most use-cases. As the blocking of a host is only temporary, the behavior differs from the one in the MariaDB server. This allows the number of failures to be set to a much lower value negating some of the problems caused by the relatively simple implementation.
This commit is contained in:
@ -186,6 +186,16 @@ public:
|
|||||||
return m_type;
|
return m_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark authentication as failed
|
||||||
|
*
|
||||||
|
* This updates the number of failures that have occurred from this host. If the number of authentications
|
||||||
|
* exceeds a certain value, any attempts to connect from the remote in quesion will be rejected.
|
||||||
|
*
|
||||||
|
* @param remote The address where the connection originated
|
||||||
|
*/
|
||||||
|
void mark_auth_as_failed(const std::string& remote);
|
||||||
|
|
||||||
// Functions that are temporarily public
|
// Functions that are temporarily public
|
||||||
bool create_listener_config(const char* filename);
|
bool create_listener_config(const char* filename);
|
||||||
struct users* users() const;
|
struct users* users() const;
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@ -40,6 +41,9 @@
|
|||||||
#include "internal/modules.hh"
|
#include "internal/modules.hh"
|
||||||
#include "internal/session.hh"
|
#include "internal/session.hh"
|
||||||
|
|
||||||
|
using Clock = std::chrono::steady_clock;
|
||||||
|
using std::chrono::seconds;
|
||||||
|
|
||||||
static std::list<SListener> all_listeners;
|
static std::list<SListener> all_listeners;
|
||||||
static std::mutex listener_lock;
|
static std::mutex listener_lock;
|
||||||
|
|
||||||
@ -47,6 +51,56 @@ static RSA* rsa_512 = NULL;
|
|||||||
static RSA* rsa_1024 = NULL;
|
static RSA* rsa_1024 = NULL;
|
||||||
static RSA* tmp_rsa_callback(SSL* s, int is_export, int keylength);
|
static RSA* tmp_rsa_callback(SSL* s, int is_export, int keylength);
|
||||||
|
|
||||||
|
// TODO: Make these configurable
|
||||||
|
constexpr int MAX_FAILURES = 25;
|
||||||
|
constexpr int BLOCK_TIME = 60;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
class RateLimit
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void auth_failed(const std::string& remote)
|
||||||
|
{
|
||||||
|
auto& u = m_failures[remote];
|
||||||
|
u.last_failure = Clock::now();
|
||||||
|
u.failures++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_blocked(const std::string& remote)
|
||||||
|
{
|
||||||
|
bool rval = false;
|
||||||
|
auto it = m_failures.find(remote);
|
||||||
|
|
||||||
|
if (it != m_failures.end())
|
||||||
|
{
|
||||||
|
auto& u = it->second;
|
||||||
|
|
||||||
|
if (Clock::now() - u.last_failure > seconds(BLOCK_TIME))
|
||||||
|
{
|
||||||
|
u.last_failure = Clock::now();
|
||||||
|
u.failures = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
rval = u.failures >= MAX_FAILURES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Failure
|
||||||
|
{
|
||||||
|
Clock::time_point last_failure = Clock::now();
|
||||||
|
uint32_t failures = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_map<std::string, Failure> m_failures;
|
||||||
|
};
|
||||||
|
|
||||||
|
thread_local RateLimit rate_limit;
|
||||||
|
}
|
||||||
|
|
||||||
Listener::Listener(SERVICE* service, const std::string& name, const std::string& address,
|
Listener::Listener(SERVICE* service, const std::string& name, const std::string& address,
|
||||||
uint16_t port, const std::string& protocol, const std::string& authenticator,
|
uint16_t port, const std::string& protocol, const std::string& authenticator,
|
||||||
const std::string& auth_opts, void* auth_instance, SSL_LISTENER* ssl)
|
const std::string& auth_opts, void* auth_instance, SSL_LISTENER* ssl)
|
||||||
@ -968,6 +1022,12 @@ static ClientConn accept_one_connection(int fd)
|
|||||||
}
|
}
|
||||||
|
|
||||||
configure_network_socket(conn.fd, conn.addr.ss_family);
|
configure_network_socket(conn.fd, conn.addr.ss_family);
|
||||||
|
|
||||||
|
if (rate_limit.is_blocked(conn.host))
|
||||||
|
{
|
||||||
|
close(conn.fd);
|
||||||
|
conn.fd = -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (errno != EAGAIN && errno != EWOULDBLOCK)
|
else if (errno != EAGAIN && errno != EWOULDBLOCK)
|
||||||
{
|
{
|
||||||
@ -1177,3 +1237,8 @@ void Listener::accept_connections()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Listener::mark_auth_as_failed(const std::string& remote)
|
||||||
|
{
|
||||||
|
rate_limit.auth_failed(remote);
|
||||||
|
}
|
||||||
|
@ -818,6 +818,8 @@ static int gw_read_do_authentication(DCB* dcb, GWBUF* read_buffer, int nbytes_re
|
|||||||
{
|
{
|
||||||
protocol->protocol_auth_state = MXS_AUTH_STATE_FAILED;
|
protocol->protocol_auth_state = MXS_AUTH_STATE_FAILED;
|
||||||
mysql_client_auth_error_handling(dcb, auth_val, next_sequence);
|
mysql_client_auth_error_handling(dcb, auth_val, next_sequence);
|
||||||
|
mxb_assert(dcb->session->listener);
|
||||||
|
dcb->session->listener->mark_auth_as_failed(dcb->remote);
|
||||||
/**
|
/**
|
||||||
* Close DCB and which will release MYSQL_session
|
* Close DCB and which will release MYSQL_session
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user