5 月 292021
 

Source: 被我誤解的max_connect_errors
Source: MySQL參數max_connect_errors分析釋疑

第一節 什麼是max_connect_errors

一開始接觸這個參數的時候,感覺他和max_connections的含義差不多,字面意思簡單明瞭,這個參數的含義是最大連接錯誤數,翻翻mysql的文檔中的解釋是If more than this many successive connection requests from a host are interrupted without a successful connection, the server blocks that host from further connections,大意是:如果mysql服務器連續接收到了來自於同一個主機的請求,且這些連續的請求全部都沒有成功的建立連接就被斷開了,當這些連續的請求的累計值大於 max_connect_errors的設定值時,mysql服務器就會阻止這臺主機後續的所有請求。”without a successful connection”那太好辦了,故意輸錯密碼不就行了,並且網上搜索了下該參數的說明,大量的文章充斥著” 防止暴力破解密碼”的內容,於是興高采烈的去做了測試。以下測試基於自建的mysql(非rds for mysql)

第二節 測試max_connect_errors

1,創建賬號:

2,設置max_connect_errors為3:

mysql> show variables like '%max_connect_errors%';
mysql> set global max_connect_errors=3;

3,故意輸錯密碼3次,第四次使用正確密碼登錄進行驗證:

[root@mytestlnx02 tmp]# mysql -h10.20.57.24 -utest -p
 Enter password:
 ERROR 1045 (28000): Access denied for user 'test'@'mytestlnx02' (using password: YES)
[root@mytestlnx02 tmp]# mysql -h10.20.57.24 -utest -p
 Enter password:
 ERROR 1045 (28000): Access denied for user 'test'@'mytestlnx02' (using password: YES)
[root@mytestlnx02 tmp]# mysql -h10.20.57.24 -utest -p
 Enter password:
 ERROR 1045 (28000): Access denied for user 'test'@'mytestlnx02' (using password: YES)
[root@mytestlnx02 tmp]# mysql -h10.20.57.24 -utest -p
 Enter password:
 ERROR 1045 (28000): Access denied for user 'test'@'mytestlnx02' (using password: YES)

4,結論是第四次依然可以登錄,即密碼不對(認證失敗)不屬於” ”without a successful connection””的範疇,網上的” 防止暴力破解密碼”也不成立了。

其實,關於某個IP輸入了錯誤密碼,MySQL會在performance_schema數據庫下的host_cache表中記錄。它會累計記錄在COUNT_AUTHENTICATION_ERRORS字段,如下所示:

mysql> select COUNT_AUTHENTICATION_ERRORS from performance_schema.host_cache;

第三節 繼續分析max_connect_errors

再繼續看文檔,發現還有以下說明:

You can unblock blocked hosts by flushing the host cache. To do so, issue a FLUSH HOSTS statement or execute a mysqladmin flush-hosts command.

大意是:

當你遇到主機被阻止的時候,你可以清空host cache來解決,具體的清空方法是執行flush hosts或者在mysql服務器的shell裡執行 mysqladmin flush-hosts操作

既然清空host cache可以解決主機被阻止訪問的問題,那應該與host cache有些關係,看看host cache的介紹可能會有些眉目,關於host cache,文檔解釋如下:

The MySQL server maintains a host cache in memory that contains information about clients: IP address, host name, and error information. The server uses this cache for nonlocal TCP connections. It does not use the cache for TCP connections established using a loopback interface address (127.0.0.1 or ::1), or for connections established using a Unix socket file, named pipe, or shared memory.

大意是:

Mysql服務器會在內存裡管理一個host cache,host cache裡保存了一些客戶端的ip地址,主機名,以及這個客戶端在與server建立連接時遇到的一些錯誤信息,host cache對不是本地的TCP連接才有效,所以host cache對127.0.0.1 或者::1是無效的,並且對於Unix socket file、named pipe以及 shared memory方式建立的連接也是無效的。並且通過了解,host cache的內容可以通過performance_schema.host_cache來查看,通過performance_schema.host_cache表裡的幾個列的描述信息,對之前的測試不成立的原因有些瞭解了,部分相關列如下:

IP
The IP address of the client that connected to the server, expressed as a string.
連接到mysql server的主機的連接地址

HOST
The resolved DNS host name for that client IP, or NULL if the name is unknown.
通過dns解析IP地址獲取到的該IP地址對應的mysql client的主機名

SUM_CONNECT_ERRORS
The number of connection errors that are deemed “blocking” (assessed against the max_connect_errors system variable). Only protocol handshake errors are counted, and only for hosts that passed validation (HOST_VALIDATED = YES).

COUNT_HANDSHAKE_ERRORS
The number of errors detected at the wire protocol level.

通過SUM_CONNECT_ERRORS(連接錯誤計數)描述,重點是紅色部分:只計算協議握手過程的錯誤(Only protocol handshake errors are counted),也就是說max_connect_errors 可能記錄的是協議(不確定是tcp協議還是應用協議,通過抓包以及COUNT_HANDSHAKE_ERRORS的” the wire protocol level”說明可能是指應用協議)的握手過程中出現的錯誤 ,也就是可以說網絡不好(無法順利握手)會導致該問題。

第四節 繼續測試max_connect_errors

通過之前的說明,需要模擬應用協議握手失敗的情況,最後考慮使用telnet一些來做測試

1,創建賬號

2,設置max_connect_errors為3:

3,先使用telnet 10.26.254.217 3306連接3次,第四次使用正確的賬號密碼嘗試登陸

telnet前查看performance_schema.host_cache的記錄為空

mysql> select IP,SUM_CONNECT_ERRORS,COUNT_HANDSHAKE_ERRORS from performance_schema.host_cache;

第一次 telnet 10.26.254.217 3306

第二次 telnet 10.26.254.217 3306

第三次 telnet 10.26.254.217 3306

mysql> select IP,SUM_CONNECT_ERRORS,COUNT_HANDSHAKE_ERRORS from performance_schema.host_cache;
+---------------+--------------------+------------------------+
 | IP            | SUM_CONNECT_ERRORS | COUNT_HANDSHAKE_ERRORS |
 +---------------+--------------------+------------------------+
 | 192.168.4.189 |                  3 |                      3 |
 +---------------+--------------------+------------------------+

第四次執行 mysql -h10.26.254.217 -utestcon -p123 -P3306

問題復現了,出現了錯誤提示ERROR 1129 (HY000): Host ‘10.24.236.231’ is blocked because of many connection errors; unblock with ‘mysqladmin flush-hosts’

第五節 ERROR 1129 (HY000)問題延伸

解決ERROR 1129 (HY000)的方法是執行flush host或者 mysqladmin flush-hosts,其目的是為了清空host cache裡的信息,那是不是說不使用host cache就可以了?使host cache不生效的方式有如下兩種:

  1. 設置 host_cache_size 為0
  2. 打開skip-name-resolve

需要通過測試看下推測是否生效

5.1 設置 host_cache_size 為0

mysql> set global host_cache_size=0;

2,再次查詢performance_schema.host_cache

3,繼續之前的測試:先使用telnet 10.26.254.217 3306連接3次,第四次使用正確的賬號密碼嘗試登陸

更改已經生效,max_connect_errors的作用無效了

5.2 打開skip-name-resolve

1,在cnf配置文件裡設置skip-name-resolve 以此打開skip-name-resolve

mysql> show variables like '%skip-name-resolve%';

2,繼續之前的測試:先使用telnet 10.26.254.217 3306連接3次,第四次使用正確的賬號密碼嘗試登陸

3,查詢performance_schema.host_cache

更改已經生效,max_connect_errors的作用無效了,RDS for mysql 的skip_name_resolve是on的狀態,所以很少會出現ERROR 1129 (HY000)的錯誤


官方資料介紹,host_cache的字段是統計被視為「阻塞」的連接錯誤的數量(根據max_connect_errors系統變量進行評估)。 只計算協議握手錯誤,並且僅用於通過驗證的主機(HOST_VALIDATED = YES)。

SUM_CONNECT_ERRORS
The number of connection errors that are deemed 「blocking」 (assessed against the max_connect_errors system variable). Only protocol handshake errors are counted, and only for hosts that passed validation (HOST_VALIDATED = YES).

MySQL客戶端與數據庫建立連接需要發起三次握手協議,正常情況下,這個時間非常短,但是一旦網絡異常,網絡超時等因素出現,就會導致這個握手協議無法完成,MySQL有個參數connect_timeout,它是MySQL服務端進程mysqld等待連接建立完成的時間,單位為秒。如果超過connect_timeout時間範圍內,仍然無法完成協議握手話,MySQL客戶端會收到異常,異常消息類似於: Lost connection to MySQL server at ‘XXX’, system error: errno,該變量默認是10秒:

mysql> show variables like 'connect_timeout';
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| connect_timeout | 10 |
+-----------------+-------+
1 row in set (0.00 sec)

那麼我們就構造一個網絡超時引起的數據庫連接被中斷案例吧,我們用Linux下的netem與tc命令模擬構造出複雜環境下的網絡傳輸延時案例,如下設置後,此時從測試服務器去訪問MySQL服務器,都會出現延時11秒:

# tc qdisc add dev enp0s3 root netem delay 11000ms
# ping 10.20.57.24
 PING 10.20.57.24 (10.20.57.24) 56(84) bytes of data.
 64 bytes from 10.20.57.24: icmp_seq=1 ttl=62 time=0.251 ms
 64 bytes from 10.20.57.24: icmp_seq=2 ttl=62 time=0.330 ms
 64 bytes from 10.20.57.24: icmp_seq=3 ttl=62 time=0.362 ms
 64 bytes from 10.20.57.24: icmp_seq=4 ttl=62 time=0.316 ms
 64 bytes from 10.20.57.24: icmp_seq=5 ttl=62 time=0.281 ms

我們在測試服務器gettestlnx02連接MySQL數據庫,如下所示(注意,如果你是在通過ssh連接這台服務器的話,此時在gettestlnx02上操作會相當慢。當然你也可以在MySQL服務器模擬網絡延時,或者你將connect_timeout和網絡延時都設小一點)

# mysql -h10.20.57.24 -utest -p
Enter password:
ERROR 2013 (HY000): Lost connection to MySQL server at 'reading authorization packet', system error: 0

如上所示,由於網絡延時超過10秒,導致連接MySQL失敗,此時,你在MySQL服務器上查詢host_cache表時,那麼你就會看到SUM_CONNECT_ERRORS變成1了,COUNT_HANDSHAKE_ERRORS也變成了1.

那麼我們反複這樣折騰三次,那麼你會看到SUM_CONNECT_ERRORS變成3了,COUNT_HANDSHAKE_ERRORS也變成了3了.

然後我們用netem與tc 命令在測試服務器上取消網絡延遲模擬,然後去測試連接MySQL數據庫,如下測試所示:

# tc qdisc del dev eth0 root netem delay 11000ms
# mysql -h10.20.57.24 -utest -p
Enter password:
ERROR 1129 (HY000): Host '192.168.27.180' is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts'

此時就能構造出「ERROR 1129 (HY000): Host ‘192.168.27.180’ is blocked because of many connection errors; unblock with ‘mysqladmin flush-hosts’」錯誤了。

解決方案

解決ERROR 1129 (00000): Host ‘xxx’ is blocked because of many connection errors. Unblock with ‘mysqladmin flush-hosts’這個錯誤的方法比較多,不過有些方案都是臨時方案。臨時方案是指標不治本。關鍵還是需要解決網絡錯誤(這個往往需要求助網絡管理人員或系統管理員)

解決方法:

1、將變量max_connection_errors的值設置為一個更大的值

mysql> show variables like 'max_connect_errors';
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| max_connect_errors | 3 |
+--------------------+-------+
1 row in set (0.01 sec)
mysql> set global max_connect_errors=150;
Query OK, 0 rows affected (0.00 sec)

這個臨時方案只是延遲觸發IP被禁止訪問的條件而已,而且在複雜情況或高並發的情況下,需要設置一個很大的值,否則很容易就會再次被觸發。另外,變量只對當前環境生效,如果重啟就會失效,如果需要永久有效,可以在my.cnf配置文件裏面配置。

2:使用flush hosts

mysql> flush hosts;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from performance_schema.host_cache;
Empty set (0.00 sec)

當然你也可以mysqladmin flush-hosts 命令清理一下hosts cache信息

[root@DB-Server ~]# mysqladmin --port=3306 -uroot -p flush-host

那麼host cache是什麼呢? 官方介紹如下:

The MySQL server maintains a host cache in memory that contains information about clients: IP address, host name, and error information. The server uses this cache for nonlocal TCP connections. It does not use the cache for TCP connections established using a loopback interface address (127.0.0.1 or ::1), or for connections established using a Unix socket file, named pipe, or shared memory.

簡單來說,就是MySQL服務器在內存中維護一個包含客戶端信息的緩存:IP地址,主機名和錯誤信息等。 服務器會將非本地TCP連接信息緩存起來。它不會緩存使用環回接口地址(127.0.0.1或者:: 1)建立的TCP連接,或者使用Unix套接字文件,命名管道或共享內存建立的連接。host cache信息可以通過performance_schema數據庫下的host_cache表查詢。

3:將變量host_cache_size設置為0

其實我想說這是一個最不靠譜的解決方法,只是讓MySQL服務器不記錄host cache信息而已。完全可以忽略這個方法。

mysql> show variables like '%host_cache_size%';
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| host_cache_size | 279 |
+-----------------+-------+
1 row in set (0.00 sec)
mysql> set global host_cache_size=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from performance_schema.host_cache;
Empty set (0.00 sec) 

知識點延伸

關於參數max_connect_errors,不要誤解其功能,這個不能作為防止窮舉密碼攻擊的手段。如果您擔心SYN泛濫攻擊,max_connect_errors可能會在特定情況下幫助您。 MySQL 5.6中的PERFORMANCE_SCHEMA改進提供了有關潛在強力攻擊的有意義的信息,但是只有在涉及主機緩存的情況下。關於這個可以參考博文Understanding max_connect_errors

參考資料:

 Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)

CAPTCHA Image
Play CAPTCHA Audio
Reload Image