MySQL 本机 IP 被封锁:Zabbix Proxy 4.0 的短连接
摘要
- 一台 MySQL 8.4.10 上,本机网卡 IP 因握手中断被写进
host_cache。用tcpconnect、tcplife和tcpdump追到同机的 Zabbix Proxy 4.0 之后,给出三种处理:停掉短连接、改初始握手插件、升级 Proxy 的客户端库 - 本文按 MySQL 8.4.10、Zabbix Proxy 4.0。
bcc-tools在 RHEL/CentOS 8+、Fedora 上用dnf安装,CentOS 7 上用yum - 解除封锁见 MySQL 解除 Host is blocked。Proxy 的库配置见 Zabbix proxy,4.0 的已知问题见 Known issues。初始握手插件名见 Bug #118447,8.4.8 的修复记录见 Changes in MySQL 8.4.8
计数先看频率
performance_schema.host_cache 里只有本机这一行,完整输出见 MySQL 解除 Host is blocked。和这次有关的字段是:
| 字段 | 值 |
|---|---|
IP |
10.10.0.220 |
HOST |
ip-10-10-0-220.cn-northwest-1.compute.internal |
HOST_VALIDATED |
YES |
SUM_CONNECT_ERRORS |
1000 |
COUNT_HANDSHAKE_ERRORS |
1000 |
COUNT_HOST_BLOCKED_ERRORS |
242 |
COUNT_AUTHENTICATION_ERRORS |
0 |
FIRST_ERROR_SEEN |
2026-11-11 09:57:33 |
LAST_ERROR_SEEN |
2026-11-12 06:38:33 |
当时 max_connect_errors 是 1000。SUM_CONNECT_ERRORS 与 COUNT_HANDSHAKE_ERRORS 同为 1000,这 1000 次都是协议握手中断。到达阈值之后又有 242 次连接被拒绝,记在 COUNT_HOST_BLOCKED_ERRORS。认证、SSL、账号权限、连接数配额的计数都是 0。
1000 + 242 = 1242 次尝试。09:57:33 到次日 06:38:33 相差 20 小时 41 分,也就是 1241 分钟。1242 次首尾相接,平均间隔是 1 分钟。
Zabbix Proxy 自己的库连接如果失败,会按更短的间隔重试,并在 /var/log/zabbix/zabbix_proxy.log 里写 [Z3001]。这次日志里没有连接失败,proxy 的业务也在继续。每分钟一次、日志又是干净的,说明这条 TCP 不是 proxy 那条正在用的库会话。
定位到 zabbix_proxy
同机还跑着 zabbix_proxy 和 zabbix_agentd。短连接只有零点几毫秒,ss 经常看空。bcc-tools 可以在 connect() 发生时把进程名打出来。
1 | # RHEL/CentOS 8+、Fedora |
安装后的程序在 /usr/share/bcc/tools/,不在默认 PATH 里。-P 3306 只看目标端口是 3306 的 connect()。
1 | sudo /usr/share/bcc/tools/tcpconnect -P 3306 |
1 | PID COMM IP SADDR DADDR DPORT |
tcplife 看这条连接活了多久、应用读写了多少。-D 3306 只看远端端口 3306。
1 | sudo /usr/share/bcc/tools/tcplife -D 3306 |
1 | PID COMM LADDR LPORT RADDR RPORT TX_KB RX_KB MS |
进程是 zabbix_proxy,源和目的都是本机网卡 IP,存活 0.21 毫秒,应用层发送和接收都是 0 KB。zabbix_agentd 没有出现在这两条输出里。
配置和账号插件
/etc/zabbix/zabbix_proxy.conf 里和库有关的项是:
1 | DBHost=localhost |
口令在 DBPassword,这里不记录。Zabbix 4.0 文档写明:DBHost=localhost 时 MySQL 走 Unix socket,DBPort 用于不走 socket 的连接。socket 连接不进入 host_cache。抓到的目的地址是 10.10.0.220:3306,这是一条 TCP,对不上 DBHost=localhost 这条 socket 路径。
账号上的插件已经是 mysql_native_password:
1 | SELECT user, host, plugin FROM mysql.user WHERE user = 'zabbix_proxy'; |
1 | +--------------+-----------+-----------------------+ |
mysql.user.plugin 决定的是握手进行到密码校验时用哪一种插件。服务器在这之前发出的初始握手包,写的是自己的默认插件名。8.4 的默认策略 authentication_policy 是 *,,,第一因素的默认插件是 caching_sha2_password。账号改成 mysql_native_password,不会改掉这个问候包。
本机若已经能用这个账号连上,说明服务器已经加载了 mysql_native_password。8.4 里这个插件默认关闭,启动参数是 mysql_native_password=ON。没加这个参数就重启,这些账号会变成插件未加载。
抓包里的顺序
本机访问自己的网卡 IP 时,包出现在 lo 上。MySQL 看到的对端仍是 10.10.0.220,所以会计入 host_cache。127.0.0.1 和 ::1 不进这张缓存。
1 | sudo tcpdump -i lo -nn -A 'tcp port 3306 and host 10.10.0.220' -c 50 |
一次失败连接的标志位顺序是:
1 | 10.10.0.220.53724 > 10.10.0.220.3306: Flags [S] |
三次握手完成后,客户端大约 21 微秒就发了 FIN。又过大约 260 微秒,服务器才送出 78 字节的初始握手包,明文里有版本 8.4.10 和 caching_sha2_password。客户端随后发 RST。本端已经关闭、又收到数据时,内核就会回 RST。
tcplife 的 RX_KB 是 0,应用没有读到这 78 字节。客户端库若是读到 caching_sha2_password 再拒绝,会先发生一次读,并在 proxy 日志里留下:
1 | [Z3001] connection to database 'zabbix_proxy' failed: [2059] Authentication plugin 'caching_sha2_password' cannot be loaded |
这次日志里没有这行。问候包里出现 caching_sha2_password,只说明 8.4 对每个新 TCP 连接都这样宣告默认插件;这条连接在宣告送到之前已经开始关闭。
老的客户端库仍然是另一类失败。Zabbix 4.0 的包常见链接 libmysqlclient.so.18(MySQL 5.6 客户端)或 libmysqlclient.so.20(MySQL 5.7 客户端)。这类库没有 caching_sha2_password,也不处理服务器随后发来的 AuthSwitchRequest。账号即使已经是 mysql_native_password,只要问候包先写了它不认识的插件名,连接会在密码校验之前失败。那一类失败读得进握手包,日志里有 2059。用正在运行的二进制确认链接的库:
1 | # PID 换成 tcpconnect 看到的那个 |
libmysqlclient.so.21 是 MySQL 8.0 及以后常见的库名。
主连接为什么还在
host_cache 只统计非本机回环的 TCP。Unix socket、127.0.0.1、::1 都不在里面。DBHost=localhost 的主会话走 socket 时,proxy 日志可以一直正常,同时网卡 IP 上的短连接把 10.10.0.220 累加到封锁阈值。
先看 socket 文件路径,再看 proxy 是否还连着它:
1 | SHOW VARIABLES LIKE 'socket'; |
1 | # 输出里有 zabbix_proxy 和 mysql.sock 时,主会话在 socket 上 |
被封锁之后,受影响的是源地址为 10.10.0.220 的 TCP 客户端。同一台机器上用这个 IP 访问本机 3306 的程序会一起被拒绝。走 socket 或 127.0.0.1 的连接不受这行缓存影响。
TRUNCATE TABLE performance_schema.host_cache 只清掉当前计数。短连接还在时,大约每分钟加 1 次。阈值 1000 时,大约 16 小时会再次封锁这个 IP。把 max_connect_errors 调大,只是把下一次封锁往后推。
为什么 3306 端口监控没有报警
这次的触发器是:
1 | {ip-10-10-0-220.cn-northwest-1.compute.internal:net.tcp.listen[3306].last(#2)}=0 |
net.tcp.listen[3306] 由 zabbix_agentd 在本机查看 3306 是否处于 LISTEN。有进程监听就是 1,没有才是 0。它不向 3306 发起 connect(),也不登录 MySQL。mysqld 一直在监听,历史值就一直是 1。Host is blocked 只拒绝后续 TCP 连接,不会把监听套接字从 LISTEN 里拿掉,所以这项的值不会变成 0。
last(#2) 取的是倒数第二条,表达式只在那一条等于 0 时成立。它不表示「最近两次都是 0」。这次倒数第二条也是 1,触发器不会动作。
同一主机上还有一条已启用的简单检查,更新间隔 60 秒,键值是:
1 | net.tcp.service[tcp,{HOST.CONN},3306] |
{HOST.CONN} 会换成这台主机的连接地址,这里就是 10.10.0.220。简单检查由监控它的 zabbix_proxy 执行:对这个地址的 3306 做一次 connect(),成功就记 1,然后关闭。这和 tcpconnect 里每分钟一条、进程名为 zabbix_proxy、连上就关的记录一致。
这条项的值也是 1。TCP 三次握手成功,简单检查就认为服务可用,它不读 MySQL 问候包,也看不到错误号 1129。IP 被封锁之后,服务器仍先接受 TCP,再在协议层拒绝,connect() 已经成功,这项继续是 1。挂在它上面的触发器如果也是「值等于 0」,同样不会动作。
要让「MySQL 不接受登录」触发报警,监控项需要走到协议或账号,例如带用户名的 mysql.ping。
三种处理办法
三种办法对上不同现象,可以只做第 1 种,也可以和第 2、第 3 种一起做。
-
停掉短连接:这次这种每分钟一次、连上就关的 TCP。停掉之后,
host_cache不再跟着涨。 -
改初始握手插件:客户端已经读了问候包,日志里有
Authentication plugin 'caching_sha2_password' cannot be loaded。让 8.4.10 的问候包改口mysql_native_password。 -
升级 Proxy 的客户端库:换掉
libmysqlclient.so.18或so.20。监控项里的端口探测还要靠第 1 种停掉。
处理办法 1:停掉每分钟一次的短连接
每分钟那条短连接就是简单检查 net.tcp.service[tcp,{HOST.CONN},3306]。net.tcp.listen[3306] 只查监听表,留着不影响 host_cache。
停用这条简单检查,或者把地址从 {HOST.CONN} 改成 127.0.0.1。Proxy 和 MySQL 在同一台机器上时,127.0.0.1 是回环地址,不进入 host_cache,端口是否接受 TCP 仍然每分钟查一次:
1 | net.tcp.service[tcp,127.0.0.1,3306] |
要核对是不是它,用 70 秒的 strace 盖住一个采集周期,只跟踪 connect:
1 | # PID 换成 tcpconnect 看到的那个。输出里应出现 10.10.0.220:3306 |
停用或改地址之后再查一次计数。新的 SUM_CONNECT_ERRORS 不再随着分钟增加,这条来源就断了。已经封锁的 IP 还要用 MySQL 解除 Host is blocked 里的 TRUNCATE 解开。
再查本机名字解析,避免把 localhost 指到网卡 IP:
1 | getent hosts localhost |
localhost 应解析到 127.0.0.1 或 ::1。libmysqlclient 对主机名字符串 localhost 直接走 socket,不会先做这次 DNS。
处理办法 2:初始握手改用 mysql_native_password
这一办法只处理「客户端读了问候包,因不认识 caching_sha2_password 而失败」。处理办法 1 里那种在读包之前就发 FIN 的连接,改问候里的插件名不会让它消失。
8.4 已经删除 default_authentication_plugin。把它写进 my.cnf 后,mysqld 会因为未知变量起不来。替代变量是 authentication_policy。8.4.0 到 8.4.7 还有 Bug #118447:策略写成 mysql_native_password 时,初始握手仍固定发送 caching_sha2_password。8.4.8 起修复,本文的 8.4.10 包含这个修复。
先看当前策略。默认值 *,, 的第一因素就是 caching_sha2_password。
1 | SHOW VARIABLES LIKE 'authentication_policy'; |
插件已经加载时,先只改当前进程,让新连接的问候包改口:
1 | -- 当前进程的新连接立即使用。重启后回到配置文件或默认值 *,, |
语句若报错,停在这一步,再用上面的 SHOW VARIABLES 确认值没有被改掉。改成功后,用一个仍采用 caching_sha2_password 的账号登录一次,确认没有被新策略挡住,然后抓一次新连接的握手包,明文里应是 mysql_native_password。
mysql_native_password 在 8.4 默认关闭,MySQL 9.0 已删除,只适合给旧客户端过渡。SET GLOBAL 不写文件,重启后回到 *,,。写入配置文件时要同时打开插件,否则重启后旧账号会因插件未加载而失败。配置文件里保留已删除的 default_authentication_plugin 时,mysqld 起不来。
重启后仍要保持这条策略时,写成:
1 | [mysqld] |
插件开关是启动参数,只 SET GLOBAL 不会把它从关闭变成打开。本机账号已经在用 mysql_native_password 时,当前进程里插件是开着的。
处理办法 3:升级 Proxy 的客户端库
Zabbix Proxy 4.0 已停止维护。换官方当前版本的 proxy 包,并用前面的 ldd 确认新进程链接的是 libmysqlclient.so.21 这一代,而不是 so.18 或 so.20。升级之后,主库会话可以认识 caching_sha2_password 和 AuthSwitchRequest。
升级不会取消监控项里的 TCP 端口探测。探测还在,host_cache 仍会按每分钟一次累加。两件事分开处理:客户端库决定主会话能不能完成认证,监控项决定有没有「连上就关」的 TCP。
做完核对
-
tcpconnect -P 3306再观察两分钟,不再出现zabbix_proxy访问10.10.0.220:3306。 -
SELECT SUM_CONNECT_ERRORS, COUNT_HOST_BLOCKED_ERRORS FROM performance_schema.host_cache WHERE IP = '10.10.0.220';不再随分钟增加。已经封锁的,先TRUNCATE再复查。 -
/var/log/zabbix/zabbix_proxy.log里没有新的[Z3001]。sudo ss -xp | grep zabbix_proxy仍能看到主会话的 socket。 -
改过
authentication_policy的,握手包明文里的插件名与SHOW VARIABLES LIKE 'authentication_policy';一致。改过配置文件的,重启后再查一次。