概述
本文档描述了Postfix SMTP服务器过载的症状。它提供了在正常运行期间避免过载的永久性 main.cf 配置更改,以及应对意外邮件洪峰的临时 main.cf 配置更改。本文件针对支持压力自适应行为的 Postfix 2.5 及更高版本提供了具体建议,并针对不支持该功能的早期 Postfix 版本提供了相应建议。
本文件涵盖的主题:
- Postfix SMTP 服务器过载的症状
- 自动压力自适应行为
- 同时处理更多 SMTP 客户端
- 减少每个 SMTP 客户端的处理时间
- 断开可疑 SMTP 客户端的连接
- 针对旧版 Postfix 的临时措施
- 检测压力自适应行为的支持情况
- 强制启用或禁用压力自适应行为
- 其他卸载僵尸客户端的措施
- 致谢 ;
Postfix SMTP 服务器过载的症状
在正常情况下,Postfix SMTP 服务器会在 SMTP 客户端连接时立即响应;邮件投递时间仅在处理大容量邮件时才明显延迟。当 SMTP 客户端数量超过 Postfix SMTP 服务器进程数量时,性能会急剧下降。当所有 Postfix SMTP 服务器进程均处于忙碌状态时,SMTP 客户端必须等待直至有可用服务器进程。
SMTP 服务器过载可能由合法邮件激增(例如 DNS 注册商为新域名开启注册)引发,也可能因误操作(转发循环导致邮件爆炸)或恶意行为(蠕虫爆发、僵尸网络或其他非法活动)引起。
Postfix SMTP 服务器过载的症状包括:
- 远程 SMTP 客户端在 Postfix 发送 "220 hostname.example.com ESMTP Postfix" 问候语前会出现长时间延迟。
- 注意:损坏的 DNS 配置也可能导致 Postfix 在发送 "220 hostname.example.com ..." 前出现长时间延迟。这些延迟在 Postfix 未超载时也存在。
- 注意:为避免终端用户邮件客户端出现"过载"延迟,请在master.cf(自Postfix 2.1起存在)中启用"submission"服务条目,并告知用户连接此服务而非公共SMTP服务。
- Postfix SMTP 服务器日志中记录了大量"连接后断开"事件。这是因为远程 SMTP 客户端在 Postfix 响应连接前断开了连接。
- 注意:扫描开放的 SMTP 端口也可能导致"连接丢失 ..."日志消息。
Postfix 2.3 及更高版本会记录一个警告,指出所有服务器端口均处于繁忙状态:
Oct 3 20:39:27 spike postfix/master[28905]: warning: service "smtp" (25) 已达到进程限制 "30":新客户端可能遇到 明显延迟 Oct 3 20:39:27 spike postfix/master[28905]: 警告:为避免此 情况,请在 master.cf 中增加进程数或减少 每个客户端的服务时间 2023年10月3日 20:39:27 峰值 postfix/master[28905]: 警告:请参阅 http://www.postfix.org/STRESS_README.html 中的 压力自适应配置设置示例
在 Postfix SMTP 服务器过载期间未送达的合法邮件并不一定丢失。只要过载条件是暂时的,这些邮件在情况恢复正常后仍应送达。
自动压力自适应行为
Postfix 2.5 版本引入了自动压力自适应行为。其工作原理如下。当 SMTP 服务器等"公共"网络服务遇到"所有服务器端口均忙"的状况时,Postfix 的 master(8) 守护进程会记录一条警告,重启服务(不中断现有网络会话),并在服务器进程命令行中以 "-o stress=yes" 参数运行服务:
80821 ?? S 0:00.24 smtpd -n smtp -t inet -u -c -o stress=yes
通常,Postfix master(8) 守护进程会以命令行参数 "-o stress="(即参数值为空)的方式运行此服务:
83326 ??S 0:00.28 smtpd -n smtp -t inet -u -c -o stress=
对于仅有本地客户端的服务,您不会看到 "-o stress" 命令行参数。这些服务包括 Postfix 内部服务(如队列管理器)以及仅监听回环接口的服务(如 after-filter SMTP 服务)。
"stress" 参数的值是使 main.cf 参数设置具有压力自适应功能的关键。以下设置是 Postfix 2.6 及更高版本的默认值。
1 smtpd_timeout = ${stress?{10}:{300}}s 2 smtpd_hard_error_limit = ${stress?{1}:{20}} 3 smtpd_junk_command_limit = ${stress?{1}:{100}} 4 # Postfix 2.6 之后添加的参数: 5 smtpd_per_record_deadline = ${stress?{yes}:{no}} 6 smtpd_starttls_timeout = ${stress?{10}:{300}}s 7 address_verify_poll_count = ${stress?{1}:{3}}
Postfix 3.0 之前的版本使用较旧的格式 ${stress?x}${stress:y} 而不是较新的格式 ${stress?{x}:{y}}。
${name?{value}:{value}}、${name?value} 和 ${name:value} 的语法在 postconf(5) 手册页的开头有详细说明。
翻译:
;
- 第 1 行:在高负载条件下,建议将 smtpd_timeout 的值设置为 10 秒,而非默认的 300 秒。根据 postfix-users 邮件列表中多位系统管理员的经验,将"正常"的 smtpd_timeout 值设置为 60 秒不太可能影响合法客户端。然而,由于其不符合 RFC 规范,该设置不太可能成为 Postfix 的默认值。将smtpd_timeout设置为10秒甚至5秒,在高负载情况下仍可让大多数合法客户端连接并发送邮件,但可能导致部分客户端的邮件发送延迟。只要此措施仅临时使用,不应有邮件丢失。
- 第 2 行:在高负载条件下,将 smtpd_hard_error_limit 设置为 1 而不是默认的 20。这将在单次错误后断开客户端连接,为其他客户端提供连接机会。然而,这可能导致合法邮件(如包含少量未主动退订的用户名的邮件列表)出现显著延迟。只要此措施仅临时使用,不应有邮件丢失。
- 第 3 行:在高负载情况下,将 smtpd_junk_command_limit 设置为 1 而不是默认的 100。这可防止客户端通过反复发送 HELO、EHLO、NOOP、RSET、VRFY 或 ETRN 命令来保持连接打开。
- 第 5 行:在高负载条件下,将 smtpd_timeout 和 smtpd_starttls_timeout,从每次读取或写入系统调用的时间限制,改为发送或接收完整记录(SMTP 命令行、SMTP 响应行、SMTP 消息内容行或 TLS 协议消息)的时间限制。
- 第 6 行:在高负载条件下,将 TLS 协议握手消息的超时限制从默认的 300 秒减少到 10 秒。参见上文的 smtpd_timeout 讨论。
- 第 7 行:在高负载条件下,不要等待最多 6 秒以完成地址验证探测。如果结果尚未存储在地址验证缓存中,则立即回复 $unverified_recipient_tempfail_action 或 $unverified_sender_tempfail_action。只要此措施仅临时使用,就不会丢失任何邮件。
注意:请记住,压力自适应功能是在高负载条件下保持部分合法邮件正常传输的紧急措施。如果在没有攻击或机器人洪水的情况下,站点达到了 SMTP 服务器进程限制,则需要提高进程限制或添加更多硬件。
同时服务更多 SMTP 客户端 ;
本节及后续内容讨论防止邮件服务器过载的永久性措施。
避免"所有服务器进程繁忙"状态的一种措施是同时服务更多SMTP客户端。为此,您需要增加Postfix SMTP服务器进程的数量。这将提升远程SMTP客户端的响应速度,前提是服务器机器具备足够的硬件和软件资源运行额外进程,且文件系统能跟上新增负载。
- 您可以通过增加default_process_limit在main. cf(下文第 3 行)中,或通过增加 SMTP 服务器在 master.cf(下文第 10 行)中的 "maxproc" 字段来实现。无论采用哪种方式,您都需要执行 "postfix reload" 命令以使更改生效。
- 进程限制超过 1000 需要 Postfix 版本 2.4 或更高,并且操作系统支持基于内核的事件过滤器(BSD kqueue(2)、Linux epoll(4) 或 Solaris /dev/poll)。
进程越多,内存消耗越大。您可以通过使用 cdb:查找表替代 Berkeley DB 的 hash: 或 btree: 表。
1 /etc/postfix/main.cf: 2 # 提高全局进程限制,自 Postfix 2.0 起为 100。 3 default_process_limit = 200 4 5 /etc/postfix/master.cf: 6 # ============================================================= 7 # 服务类型 类型 权限 运行环境 最大进程数 命令 8 # ============================================================= 9 # 仅提高 SMTP 服务进程限制。 10 smtp inet n - n - 200 smtpd
注意:较旧版本的 SMTPD_POLICY_README 文档中存在一个错误:它们配置了固定数量的策略守护进程。当您在 master.cf 中提高 SMTP 服务器的 "maxproc" 字段时,SMTP 服务器进程在连接到策略服务器进程时会报告问题,因为它们数量不足。错误示例包括 "连接被拒绝" 或 "操作超时"。
要修复此问题,请编辑master.cf,并在所有策略服务器条目中将"maxproc"字段设置为0;请参见下例中的第6行。执行"postfix reload"命令以使更改生效。
1 /etc/postfix/master.cf: 2 # ============================================================= 3 # 服务类型 权限 运行方式 启动方式 最大进程数 命令 4 # ============================================================= 5 # 禁用策略服务进程限制。 6 策略 系统 - n n - 0 启动 7 用户=nobody 参数=/some/where/policy-server
减少每个 SMTP 客户端的处理时间
当增加 SMTP 服务器进程数量不切实际时,您可以通过消除延迟来提升 Postfix 服务器响应速度。当 Postfix 每个 SMTP 会话花费的时间减少时,相同数量的 SMTP 服务器进程可在相同时间内处理更多客户端。
- 消除无功能的 RBL 查询(已不再使用的黑名单)。这些查询可能导致性能下降。Postfix 在 RBL 服务器未响应时会记录警告。
- 消除冗余 RBL 查询(人们常使用多个包含彼此的 Spamhaus RBL)。要确定 RBL 是否包含其他 RBL,请查阅记录 RBL 策略的官方网站。
- 删除header_checks和body_checks,仅保留少数紧急模式以阻止最新蠕虫爆发或反向散射邮件。有关后者的示例,请参阅BACKSCATTER_README。
将 header_checks 和 body_checks 模式分组,以避免不必要的模式匹配操作:
1 /etc/postfix/header_checks: 2 if /^Subject:/ 3 /^Subject: 邮件中发现病毒/ 拒绝 4 /^Subject: ..other../ 拒绝 5 endif 6 7 if /^Received:/ 8 /^Received: from (postfix\.org) / 拒绝接收头中伪造的客户端名称:$1 9 /^Received: from ..other../ 拒绝 .... 10 endif
断开可疑 SMTP 客户端连接
在系统负载过高时,您可以通过断开可疑客户端的连接来提升 Postfix SMTP 服务器的响应速度,从而让其他客户端有机会与 Postfix 通信。
- 使用"521"SMTP回复代码(Postfix 2.6及更高版本)或"421"(Postfix 2.3-2.5)挂断与僵尸网络相关RBL匹配的客户端,或与选定的非RBL限制(如SMTP访问映射)匹配的客户端。Postfix SMTP服务器将拒绝邮件并断开连接,而不会等待远程SMTP客户端发送QUIT命令。
要挂断被列入黑名单的僵尸主机的连接,您可以为特定 RBL 设置 Postfix SMTP 服务器拒绝代码,并为来自特定 RBL 的单个响应设置代码。以 zen.spamhaus.org 为例;您阅读本文时,相关细节可能已变更。目前,他们的文档指出,响应为 127.0.0.10 或 127.0.0.11 表示动态客户端 IP 地址,这意味着该机器很可能在运行某种机器人程序。要使用 521 响应替换默认的 554 响应,请使用类似以下内容:
1 /etc/postfix/main.cf: 2 smtpd_client_restrictions = 3 permit_mynetworks 4 reject_rbl_client zen.spamhaus.org=127.0.0.10 5 reject_rbl_client zen.spamhaus.org=127.0.0.11 6 reject_rbl_client zen.spamhaus.org 7 8 rbl_reply_maps = hash:/etc/postfix/rbl_reply_maps 9 10 /etc/postfix/rbl_reply_maps: 11 # 在 Postfix 2.3-2.5 中使用 "421" 挂断连接。 12 zen.spamhaus.org=127.0.0.10 521 4.7.1 服务不可用; 13 $rbl_class [$rbl_what] 被阻止使用 14 $rbl_domain${rbl_reason?; $rbl_reason} 15 16 zen.spamhaus.org=127.0.0.11 521 4.7.1 服务不可用; 17 $rbl_class [$rbl_what] 被阻止使用 18 $rbl_domain${rbl_reason?; $rbl_reason}
尽管上述示例显示了三个 RBL 查询(第 4-6 行),但 Postfix 只会执行一次 DNS 查询,因此不会影响性能。
- 在 Postfix 2.3-2.5 中,使用回复代码 421(521 不会导致 Postfix 断开连接)。使用 421 回复的缺点是它仅对僵尸程序和其他恶意软件有效。如果客户端运行的是真实的 MTA,则它可能会多次重新连接,直到邮件在队列中过期。当此问题出现时,请继续使用默认的 554 回复,并按照以下说明设置 "smtpd_hard_error_limit = 1"。
您可以在 Postfix 2.5 及更高版本中自动启用上述超载措施,或在包含来自 http://www.postfix.org/download.html 镜像中压力自适应行为源代码补丁的早期版本中启用。只需将第 8 行替换为:
8 rbl_reply_maps = ${stress?hash:/etc/postfix/rbl_reply_maps}
有关自动压力自适应行为的更多信息,请参阅"自动压力自适应行为"一节。
针对较旧 Postfix 版本的临时措施
如果您使用的是 Postfix 2.5 或更高版本,或者已从 http://www.postfix.org/download.html 列出的镜像应用了压力自适应行为的源代码补丁,请参阅"自动压力自适应行为"部分。
在负载过高时,可临时采取以下措施。这些措施仍允许大多数合法客户端连接并发送邮件,但可能影响部分合法客户端。
- 减少 smtpd_timeout(默认:300 秒)。根据 postfix-users 邮件列表中多位系统管理员的经验,将"正常"的 smtpd_timeout 设置为 60 秒不太可能影响合法客户端。然而,由于其不符合 RFC 规范,因此不太可能成为 Postfix 的默认设置。将smtpd_timeout设置为10秒(下文第2行)或在高负载下设置为5秒,仍可让大多数合法客户端连接并发送邮件,但可能导致部分客户端的邮件发送延迟。只要此措施仅临时使用,不应丢失任何邮件。
- 减少 smtpd_hard_error_limit(默认值:20)。在高负载情况下将此值设置为 1(如下第 3 行),可通过在单次错误后断开客户端连接,为其他客户端提供连接机会。然而,这可能导致合法邮件(如包含少量未主动退订的过期用户名的邮件列表)出现显著延迟。只要此措施仅临时使用,不应丢失任何邮件。
- 使用 smtpd_junk_command_limit 值为 1 代替默认的 100。这可防止客户端通过反复发送 NOOP 或 RSET 命令保持空闲连接。
1 /etc/postfix/main.cf: 2 smtpd_timeout = 10 3 smtpd_hard_error_limit = 1 4 smtpd_junk_command_limit = 1
采取这些措施后,只要这些措施仅临时使用,就不会丢失任何邮件。本文件的下一部分将介绍一种自动化此过程的方法。
检测对压力自适应行为的支持
要确定您的 Postfix 安装是否支持压力自适应行为,请使用 "ps" 命令并查找 smtpd 进程。当您看到 "-o stress=" 或 "-o stress=yes" 命令行选项时,Postfix 支持压力自适应行为。请记住,Postfix 绝不会在仅监听本地地址的服务器上启用压力自适应行为。
以下示例适用于 FreeBSD 或 Linux。在 Solaris、HP-UX 及其他 System-V 变体中,请使用 "ps -ef" 代替 "ps ax"。
$ ps ax|grep smtpd 83326 ?? S 0:00.28 smtpd -n smtp -t inet -u -c -o stress= 84345 ?? Ss 0:00.11 /usr/bin/perl /usr/libexec/postfix/smtpd-policy.pl
您无法使用 postconf(1) 来检测压力自适应支持。postconf(1) 命令会忽略 main.cf 中 stress 参数的存在,因为该参数在此处无效。命令行 "-o 参数" 设置始终优先于 main.cf 中的参数设置。
;
如果您在不支持的情况下在 main.cf 中配置了压力自适应行为,不会发生任何问题。进程将运行得好像压力参数始终为空值。
强制启用或禁用压力自适应行为
您可以通过在master.cf中添加 "-o stress=yes" 命令行选项手动强制启用压力自适应行为。 这在测试 SMTP 服务中的覆盖设置时可能有用。执行 "postfix reload" 命令使更改生效。
注意:在 main.cf 中设置压力参数对接受远程连接的服务无效。
1 /etc/postfix/master.cf: 2 # ============================================================= 3 # 服务类型 权限 运行环境 启动方式 最大进程数 命令 4 # ============================================================= 5 # 6 smtp inet n - n - - smtpd 7 -o stress=yes 8 -o . . .
要永久禁用特定服务的压力自适应行为,请在该服务的 master.cf 命令行中指定 "-o stress="。这可能适用于 "submission" 服务。执行 "postfix reload" 命令以使更改生效。
注意:在 main.cf 中设置 stress 参数对接受远程连接的服务无效。
1 /etc/postfix/master.cf: 2 # ============================================================= 3 # 服务类型 权限 启动方式 最大进程数 命令 4 # ============================================================= 5 # 6 submission inet n - n - - smtpd 7 -o stress= 8 -o . . .
其他措施以卸载僵尸进程 ;
postscreen(8) 守护进程,自 Postfix 2.8 起引入,为邮件服务器提供额外的过载保护。一个 postscreen(8) 进程处理多个入站 SMTP 连接,并决定哪些客户端可以与 Postfix SMTP 服务器进程通信。通过阻止垃圾邮件机器人,postscreen(8) 为合法客户端保留更多 SMTP 服务器进程,并延迟服务器过载条件的触发。
致谢
- 感谢 postfix-users 邮件列表成员分享压力自适应功能的早期使用经验。
- RBL 示例及部分其他段落内容改编自 Noel Jones 在 postfix-users 邮件列表中的帖子。
- Wietse 在本应处理其他任务时,以尽可能小的补丁实现了压力自适应行为。