@@ -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+
362378class 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 */
0 commit comments