Skip to content

Commit db9b4ad

Browse files
authored
Merge pull request #3617 from sysown/v2.x-client_err_limit
Implements feature 'Client Error Limit'
2 parents 4f94fd3 + 5caed32 commit db9b4ad

File tree

9 files changed

+1228
-2
lines changed

9 files changed

+1228
-2
lines changed

include/MySQL_Thread.h

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ enum MySQL_Thread_status_variable {
118118
st_var_aws_aurora_replicas_skipped_during_query,
119119
st_var_automatic_detected_sqli,
120120
st_var_whitelisted_sqli_fingerprint,
121+
st_var_client_host_error_killed_connections,
121122
st_var_END
122123
};
123124

@@ -320,6 +321,7 @@ struct p_th_counter {
320321
whitelisted_sqli_fingerprint,
321322
mysql_killed_backend_connections,
322323
mysql_killed_backend_queries,
324+
client_host_error_killed_connections,
323325
__size
324326
};
325327
};
@@ -359,6 +361,20 @@ struct th_metrics_map_idx {
359361
};
360362
};
361363

364+
/**
365+
* @brief Structure holding the data for a Client_Host_Cache entry.
366+
*/
367+
typedef struct _MySQL_Client_Host_Cache_Entry {
368+
/**
369+
* @brief Last time the entry was updated.
370+
*/
371+
uint64_t updated_at;
372+
/**
373+
* @brief Error count associated with the entry.
374+
*/
375+
uint32_t error_count;
376+
} MySQL_Client_Host_Cache_Entry;
377+
362378
class MySQL_Threads_Handler
363379
{
364380
private:
@@ -382,6 +398,22 @@ class MySQL_Threads_Handler
382398
// variable address
383399
// special variable : if true, further input validation is required
384400
std::unordered_map<std::string, std::tuple<bool *, bool>> VariablesPointers_bool;
401+
/**
402+
* @brief Holds the clients host cache. It keeps track of the number of
403+
* errors associated to a specific client:
404+
* - Key: client identifier, based on 'clientaddr'.
405+
* - Value: Structure of type 'MySQL_Client_Host_Cache_Entry' holding
406+
* the last time the entry was updated and the error count associated
407+
* with the client.
408+
*/
409+
std::unordered_map<std::string, MySQL_Client_Host_Cache_Entry> client_host_cache;
410+
/**
411+
* @brief Holds the mutex for accessing 'client_host_cache', since every
412+
* access can potentially perform 'read/write' operations, a regular mutex
413+
* is enough.
414+
*/
415+
pthread_mutex_t mutex_client_host_cache;
416+
385417
public:
386418
struct {
387419
int monitor_history;
@@ -430,6 +462,8 @@ class MySQL_Threads_Handler
430462
int query_retries_on_failure;
431463
bool client_multi_statements;
432464
bool connection_warming;
465+
int client_host_cache_size;
466+
int client_host_error_counts;
433467
int connect_retries_on_failure;
434468
int connect_retries_delay;
435469
int connection_delay_multiplex_ms;
@@ -549,6 +583,77 @@ class MySQL_Threads_Handler
549583
std::array<prometheus::Counter*, p_th_counter::__size> p_counter_array {};
550584
std::array<prometheus::Gauge*, p_th_gauge::__size> p_gauge_array {};
551585
} status_variables;
586+
/**
587+
* @brief Update the client host cache with the supplied 'client_sockaddr',
588+
* and the supplied 'error' parameter specifying if there was a connection
589+
* error or not.
590+
*
591+
* NOTE: This function is not safe, the supplied 'client_sockaddr' should
592+
* have been initialized by 'accept' or 'getpeername'. NULL checks are not
593+
* performed.
594+
*
595+
* @details The 'client_sockaddr' parameter is inspected, and the
596+
* 'client_host_cache' map is only updated in case of:
597+
* - 'address_family' is either 'AF_INET' or 'AF_INET6'.
598+
* - The address obtained from it isn't '127.0.0.1'.
599+
*
600+
* In case 'client_sockaddr' matches the previous description, the update
601+
* of the client host cache is performed in the following way:
602+
* 1. If the cache is full, the oldest element in the cache is searched.
603+
* In case the oldest element address doesn't match the supplied
604+
* address, the oldest element is removed.
605+
* 2. The cache is searched looking for the supplied address, in case of
606+
* being found, the entry is updated, otherwise the entry is inserted in
607+
* the cache.
608+
*
609+
* @param client_sockaddr A 'sockaddr' holding the required client information
610+
* to update the 'client_host_cache_map'.
611+
* @param error 'true' if there was an error in the connection that should be
612+
* register, 'false' otherwise.
613+
*/
614+
void update_client_host_cache(struct sockaddr* client_sockaddr, bool error);
615+
/**
616+
* @brief Retrieves the entry of the underlying 'client_host_cache' map for
617+
* the supplied 'client_sockaddr' in case of existing. In case it doesn't
618+
* exist or the supplied 'client_sockaddr' doesn't met the requirements
619+
* for being registered in the map, and zeroed 'MySQL_Client_Host_Cache_Entry'
620+
* is returned.
621+
*
622+
* NOTE: This function is not safe, the supplied 'client_sockaddr' should
623+
* have been initialized by 'accept' or 'getpeername'. NULL checks are not
624+
* performed.
625+
*
626+
* @details The 'client_sockaddr' parameter is inspected, and the
627+
* 'client_host_cache' map is only searched in case of:
628+
* - 'address_family' is either 'AF_INET' or 'AF_INET6'.
629+
* - The address obtained from it isn't '127.0.0.1'.
630+
*
631+
* @param client_sockaddr A 'sockaddr' holding the required client information
632+
* to update the 'client_host_cache_map'.
633+
* @return If found, the corresponding entry for the supplied 'client_sockaddr',
634+
* a zeroed 'MySQL_Client_Host_Cache_Entry' otherwise.
635+
*/
636+
MySQL_Client_Host_Cache_Entry find_client_host_cache(struct sockaddr* client_sockaddr);
637+
/**
638+
* @brief Delete all the entries in the 'client_host_cache' internal map.
639+
*/
640+
void flush_client_host_cache();
641+
/**
642+
* @brief Returns the current entries of 'client_host_cache' in a
643+
* 'SQLite3_result'. In case the param 'reset' is specified, the structure
644+
* is cleaned after being queried.
645+
*
646+
* @param reset If 'true' the entries of the internal structure
647+
* 'client_host_cache' will be cleaned after scrapping.
648+
*
649+
* @return SQLite3_result holding the current entries of the
650+
* 'client_host_cache'. In the following format:
651+
*
652+
* [ 'client_address', 'error_num', 'last_updated' ]
653+
*
654+
* Where 'last_updated' is the last updated time expressed in 'ns'.
655+
*/
656+
SQLite3_result* get_client_host_cache(bool reset);
552657
/**
553658
* @brief Callback to update the metrics.
554659
*/

include/proxysql_admin.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ class ProxySQL_Admin {
355355
void stats___proxysql_servers_metrics();
356356
void stats___mysql_prepared_statements_info();
357357
void stats___mysql_gtid_executed();
358+
void stats___mysql_client_host_cache(bool reset);
358359

359360
// Update prometheus metrics
360361
void p_stats___memory_metrics();

include/proxysql_structs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,8 @@ __thread bool mysql_thread___enable_client_deprecate_eof;
799799
__thread bool mysql_thread___enable_server_deprecate_eof;
800800
__thread bool mysql_thread___log_mysql_warnings_enabled;
801801
__thread bool mysql_thread___enable_load_data_local_infile;
802+
__thread int mysql_thread___client_host_cache_size;
803+
__thread int mysql_thread___client_host_error_counts;
802804

803805
/* variables used for Query Cache */
804806
__thread int mysql_thread___query_cache_size_MB;
@@ -955,6 +957,8 @@ extern __thread bool mysql_thread___enable_client_deprecate_eof;
955957
extern __thread bool mysql_thread___enable_server_deprecate_eof;
956958
extern __thread bool mysql_thread___log_mysql_warnings_enabled;
957959
extern __thread bool mysql_thread___enable_load_data_local_infile;
960+
extern __thread int mysql_thread___client_host_cache_size;
961+
extern __thread int mysql_thread___client_host_error_counts;
958962

959963
/* variables used for Query Cache */
960964
extern __thread int mysql_thread___query_cache_size_MB;

lib/MySQL_Session.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4816,6 +4816,7 @@ void MySQL_Session::handler___status_CHANGING_USER_CLIENT___STATE_CLIENT_HANDSHA
48164816
void MySQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE(PtrSize_t *pkt, bool *wrong_pass) {
48174817
bool is_encrypted = client_myds->encrypted;
48184818
bool handshake_response_return = client_myds->myprot.process_pkt_handshake_response((unsigned char *)pkt->ptr,pkt->size);
4819+
bool handshake_err = true;
48194820

48204821
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION,8,"Session=%p , DS=%p , handshake_response=%d , switching_auth_stage=%d , is_encrypted=%d , client_encrypted=%d\n", this, client_myds, handshake_response_return, client_myds->switching_auth_stage, is_encrypted, client_myds->encrypted);
48214822
if (
@@ -5001,6 +5002,7 @@ void MySQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE(
50015002
) {
50025003
// we are good!
50035004
client_myds->myprot.generate_pkt_OK(true,NULL,NULL, _pid, 0,0,0,0,NULL);
5005+
handshake_err = false;
50045006
GloMyLogger->log_audit_entry(PROXYSQL_MYSQL_AUTH_OK, this, NULL);
50055007
status=WAITING_CLIENT_DATA;
50065008
client_myds->DSS=STATE_CLIENT_AUTH_OK;
@@ -5037,6 +5039,7 @@ void MySQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE(
50375039
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION,8,"Session=%p , DS=%p . STATE_CLIENT_AUTH_OK\n", this, client_myds);
50385040
GloMyLogger->log_audit_entry(PROXYSQL_MYSQL_AUTH_OK, this, NULL);
50395041
client_myds->myprot.generate_pkt_OK(true,NULL,NULL, _pid, 0,0,0,0,NULL);
5042+
handshake_err = false;
50405043
status=WAITING_CLIENT_DATA;
50415044
client_myds->DSS=STATE_CLIENT_AUTH_OK;
50425045
}
@@ -5103,6 +5106,10 @@ void MySQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE(
51035106
__sync_add_and_fetch(&MyHGM->status.client_connections_aborted,1);
51045107
client_myds->DSS=STATE_SLEEP;
51055108
}
5109+
5110+
if (mysql_thread___client_host_cache_size) {
5111+
GloMTH->update_client_host_cache(client_myds->client_addr, handshake_err);
5112+
}
51065113
}
51075114

51085115
// Note: as commented in issue #546 and #547 , some clients ignore the status of CLIENT_MULTI_STATEMENTS

0 commit comments

Comments
 (0)