SPF 查询上限

SPF 查询次数过多:如何解决 10 次 DNS 查询上限与 permerror

为什么 SPF 记录看起来没有问题却返回 permerror:哪些机制会消耗 DNS 查询,如何统计嵌套 include 中的查询,void 查询的独立上限,以及在不影响正常邮件的前提下把总数降到 10 以内的安全做法。

概念插图:铜色框架上串着一排发着薄荷绿光的玻璃珠,细线连向几座小巧的象牙白陶瓷塔;多出的一颗灰色珠子已无法再穿上金属杆,旁边放着一把铜色卡尺。
概念插图

你的 SPF 记录看起来没有问题:为邮箱服务商、邮件群发工具和 CRM 各加了几条 include,最后以 -all 结尾。可邮件头里却出现 spf=permerror,DMARC 报告显示 SPF 出错,或者检测工具提示“too many DNS lookups”。原因通常是 SPF 标准规定最多只能有 10 个触发 DNS 查询的机制,而且每个 include 内部隐藏的查询也要计入。

本文说明哪些机制计入、哪些不计入,void 查询的独立上限,如何手工统计总数,如何在不丢失正常邮件的前提下降到 10 以内,为什么展平需要谨慎,以及为什么一个名称只能有一条 SPF 记录。文中的域名和 IP 地址均为示例。

本指南的适用范围

免费的上线准备快照只读取你输入的确切主机名上的 SPF 记录,并只统计该记录中会触发 DNS 查询的机制。它不会展开 include: 或 redirect=,不统计 void 查询,不发送邮件,也不是渗透测试。

“too many DNS lookups”是什么意思

接收服务器检查 SPF 时,会读取信封发件人域名(即 MAIL FROM 地址,后来显示为 Return-Path)上以 v=spf1 开头的 TXT 记录,并从左到右依次评估其中的机制。部分机制会让接收方再次发起 DNS 查询。RFC 7208 第 4.6.4 节规定,每次检查最多允许 10 个此类机制,包括你所引用的每条记录内部的机制。如果评估需要第 11 个,接收方必须停止并返回 permerror,表示已发布的记录无法被正确解析。

通常可以在三个地方发现这个错误:已投递邮件的 Authentication-Results 头(RFC 8601),例如 spf=permerror smtp.mailfrom=example.com;提到查询过多的退信;或者 DMARC 聚合报告中的 SPF 错误。

有两个细节容易让人困惑。接收方在评估过程中累计查询次数,并在第一个匹配的机制处停止。因此排在前面的服务发出的邮件仍可能通过,而 include 位于第 10 次查询之后的服务则会得到 permerror,问题看起来时有时无。此外,permerror 不同于 fail:SPF 只是不再起作用。只要 SPF 或 DKIM 中有一项通过,且域名与 From 地址对齐,DMARC 就能通过(RFC 9989),所以带有对齐 DKIM 签名的邮件仍能通过,只依赖 SPF 的邮件则不能。除此之外,接收方如何处理 permerror 由其自行决定。

哪些 SPF 机制计入上限,哪些不计入

机制计入 10 次上限其他需要了解的内容
include:1 次,加上被引用记录中的所有查询如果被引用的名称没有 SPF 记录,结果为 permerror
a、a:1 次查询该名称的 A 或 AAAA 记录,取决于发件方通过 IPv4 还是 IPv6 连接
mx、mx:1 次对返回的 MX 主机做地址查询另有独立的 10 次上限
ptr1 次RFC 7208 指出不应发布(SHOULD NOT)
exists:1 次如果构造出的名称有 A 记录则匹配;常与宏配合使用
redirect=1 次,加上目标记录中的所有查询记录中含有 all 机制时会被忽略
ip4:、ip6:0 次直接比较地址,无需查询
all0 次为之前未匹配的所有发件方设置结果
exp=0 次仅在结果为 fail 之后才查询,用于获取说明文本
你自己的 v=spf1 记录0 次第一次 TXT 查询不属于机制

这个额度属于整个评估过程,而不是某一条记录。一个 include 本身消耗一次查询,再加上其背后记录消耗的查询,一直计算到最深的嵌套层级。服务商会修改自己的记录,例如把地址段分散到更多层嵌套 include 中,所以即使你什么都没改,总数也可能变化。

设置这个上限,是因为 SPF 记录会让接收方代表发布者发出 DNS 查询;RFC 7208 第 11.1 节说明,否则攻击者可能借此向第三方的 DNS 发起洪泛攻击。RFC 还建议为整个检查设置至少 20 秒的时间限制。超时会得到临时错误 temperror,而不是 permerror。

第二个上限:void 查询不得超过两次

RFC 7208 还为返回空结果的查询设置了单独的上限:没有任何记录的应答(NOERROR 且应答数为零),或者名称错误(NXDOMAIN,即名称不存在)。这类查询称为 void 查询。实现应当(SHOULD)最多允许两次,超过同样会得到 permerror,即使总数仍低于 10。这样可以避免 SPF 记录让接收方去查询一长串不存在的名称。

void 查询通常来自遗留配置:

  • 旧服务器的 DNS 名称被删除后,仍保留的 a:old-web.example.com。
  • 对没有 MX 记录的名称使用 mx 或 mx:example.org。该机制不得回退到该名称的 A 记录,因此什么也查不到。
  • 网站迁移到只响应 www 的主机后,裸域名上仍保留的 a。

include: 的要求更严格。如果被引用的名称不存在或没有发布 SPF 记录,该 include 不只是算一次 void 查询,而是直接让整体结果变成 permerror(第 5.2 节)。当你已停用的服务商注销了你的记录仍在引用的域名时,就会发生这种情况。请查询记录中指向的每一个名称;查不到结果的名称应当修复或删除。

如何手工统计 SPF 查询次数

从信封发件人域名开始,可以在你所发邮件的 Return-Path 头中看到,然后递归地逐层检查记录:

  1. 读取记录:使用 dig +short TXT example.com,Windows 下使用 nslookup -type=TXT example.com。
  2. 每个 include、a、mx、ptr、exists 和 redirect 记 1 次;ip4、ip6 和 all 记 0 次。
  3. 对每个 include 和 redirect,读取其指向的记录并按同样方法计分,嵌套多深就计算多深。
  4. 把所有结果相加,并记下任何没有返回记录的名称。

下面是一条假设的记录,表面上只有五个会触发 DNS 查询的机制:

example.com.  TXT  "v=spf1 mx include:_spf.mail.example.net include:spf.crm.example.org include:news.example.net a -all"
机制查询次数原因
mx1对 example.com 的 MX 查询
include:_spf.mail.example.net4include 本身 1 次,该记录中的三个嵌套 include 共 3 次
include:spf.crm.example.org2include 本身 1 次,其中的 a: 机制 1 次
include:news.example.net3include 本身 1 次,一个嵌套 include 1 次,其中的 exists: 机制 1 次
a1为旧网站服务器遗留的条目
-all0无需查询
合计11超出上限 1 次

这个总数是最坏情况。匹配第一个 include 的发件方最多需要五次查询,可以通过。而不匹配任何前面机制的发件方会走到最后的 a,即第 11 次查询,于是伪造邮件得到的是 permerror,而不是 -all 本来要产生的 fail。每次新增发信服务时都要重新统计,平时也应定期检查,因为被引用的记录会在没有通知的情况下变化。

如何在不丢失邮件的前提下降到 10 次以内

  • 删除不再使用的 include。例如以前的邮箱服务商、已取消的群发工具、CRM 试用服务。DMARC 聚合报告会显示哪些来源确实在以你的域名发信,帮助你发现无人需要的 include。删除前先与各服务的负责人确认。
  • 检查某个 include 是否真的会被评估。SPF 是针对信封发件人域名检查的。如果某服务发出的邮件在 Return-Path 中显示的是它自己的域名,接收方检查的就是该域名的 SPF 记录,而不是你的,你的 include 对这些邮件毫无作用。移除前请先对照服务商文档确认。
  • 对自己控制的服务器,用 ip4 和 ip6 代替 a 和 mx。如果你自己的服务器从 192.0.2.10 发信,ip4:192.0.2.10 不消耗查询,而 a 要消耗一次。还要想一想 mx 是否真的需要:接收邮件的服务器不一定负责发送,如果它们属于你的邮箱服务商,对方的 include 可能已经列出了其发信服务器。服务器迁移时记得更新地址。
  • 为每个高发信量的发件方分配独立子域名。让群发或工单服务使用 news.example.com 这样的退信域名作为信封发件人,它有自己的 SPF 记录和独立的 10 次上限。SPF 不会继承,所以子域名需要有这条记录。在 DMARC 默认的宽松对齐下,news.example.com 仍与 example.com 的 From 地址对齐。许多服务把这称为自定义 return-path 或退信域名。
  • 去掉 ptr。RFC 7208 指出不应发布它:它速度慢,依赖由连接 IP 地址所有者控制的反向 DNS,而且会消耗查询次数。请用 ip4/ip6 或服务商的 include 替代。
  • 不要把调整顺序当作修复。把最繁忙的发件方移到最前面,意味着走到第 11 次查询的邮件变少,但记录对后面所有内容依然是坏的,包括本应命中 -all 的伪造邮件。

DKIM 同样能减轻压力。DMARC 只需要一项对齐的通过,而且 DKIM 通常在转发后仍然有效,SPF 则不然。请确保每个服务都用你的域名签名,然后再精简 SPF 记录。

SPF 展平:查询更少,维护更多

展平是把一个 include 替换成它当前解析出的 ip4 和 ip6 地址段。查询次数会下降,但你的记录里现在保存了别人名单的副本,而这份副本不会自动更新。

风险后果如何控制
服务商新增或更改地址段来自新地址的邮件无法通过 SPF,而且不会有任何提醒只展平发布稳定且有文档说明的地址段的服务商,并按计划定期复查
服务商不再使用某些地址段对方已放弃、之后可能被他人使用的地址,仍在你的记录中处于授权状态删除服务商不再列出的地址段
记录变长RFC 7208 建议将 SPF 应答控制在 512 字节以内;在防火墙干扰 DNS over TCP 或 EDNS0 的环境中,超出单个 UDP 数据包的应答可能被悄悄忽略只展平开销最大的少数几个 include
基础设施经常变化大型云平台会轮换发信地址保留它们的 include;例如 Microsoft 建议不要展平其 Microsoft 365 的 include

自动展平服务会按计划解析 include 并重写你的记录。这能解决地址段过期的问题,但让第三方介入了你的 DNS,而且更新失败会破坏你的 SPF。请先尝试其他办法。如果确实要展平,请记录替换了哪个 include 以及时间,定期与服务商发布的地址段比对,并在每次更改后测试 SPF(Microsoft Learn 对其客户给出的也是同样的建议)。

每个名称只能有一条 SPF 记录,拆分要拆对

一个名称必须恰好有一条以 v=spf1 开头的 TXT 记录。如果有两条,接收方无法选择,会返回 permerror(RFC 7208 第 4.5 节)。常见情况是新服务的设置指南写着“添加这条 TXT 记录”,而有人新增了第二条 SPF 记录,而不是修改第一条。所以第二条记录不会让额度翻倍,请把两条合并:

# 错误:同一个名称上有两条 SPF 记录
example.com.  TXT  "v=spf1 include:_spf.mail.example.net -all"
example.com.  TXT  "v=spf1 include:spf.crm.example.org ~all"

# 正确:只有一条记录
example.com.  TXT  "v=spf1 include:_spf.mail.example.net include:spf.crm.example.org -all"

同一名称上的其他 TXT 记录,例如网站验证令牌,没有问题;只有以 v=spf1 开头的记录才算数。TXT 记录中的单个字符串最多 255 个字符。更长的 SPF 记录仍然是一条 TXT 记录,由多个带引号的字符串组成,接收方会不加空格地把它们拼接起来(第 3.3 节),所以在某个机制结束的位置,要用空格结束该字符串:

example.com.  TXT  "v=spf1 ip4:192.0.2.0/24 ip6:2001:db8::/32 include:_spf.mail.example.net " "include:spf.crm.example.org -all"

其他几种错误也会产生同样的 permerror:记录中任何位置的语法错误,例如把 include: 写成 include=(第 4.6 节),或者 include 了一个没有 SPF 记录的域名。SPF 记录同样不会继承,所以每个用作信封发件人的名称都需要自己的记录,并且各自有 10 次的上限。

免费 SPF 和 DMARC 检测工具统计什么

免费的上线准备快照无需注册,对一个公开域名运行八项被动检查,其中一项读取邮件相关的 DNS 记录。它不是渗透测试,也不是六个领域的审计,并且不会发送邮件。

  • 确切的主机名。它读取你输入的名称上的 SPF 记录,不会回退到上级域名。请输入信封发件人域名本身,例如 example.com,而不是 www.example.com。
  • 顶层计数。它统计该记录中会触发 DNS 查询的机制(include、a、mx、ptr、exists 和 redirect),超过十个时标记为中等风险信号。
  • 不展开。它不会展开 include: 或 redirect=,不统计 void 查询,也不会标记同一名称上的第二条 SPF 记录。上面那个五个机制的例子能通过这项计数,尽管接收方需要 11 次查询,所以请同时自己做递归统计。
  • 其他 SPF 信号。+all 被标记为高风险,?all 为低风险;当主机有 MX 记录而缺少 SPF 或 DMARC 记录时,也会被标记。

如果想先查看邮件 DNS 检查,请使用免费的 SPF 和 DMARC 检测工具:它运行同一个快照,并把这项检查显示在其他七项之前。至于记录的其余部分,从 ~all 与 -all 的选择到把 DMARC 从 none 提升到 reject,请参阅 SPF 和 DMARC 指南。

常见问题

“SPF permerror: too many DNS lookups”是什么意思?

评估你的 SPF 记录需要超过 10 个会触发 DNS 查询的机制,包括被引用记录内部的机制。RFC 7208 要求接收方此时停止并返回 permerror,因此 SPF 无法再帮助邮件通过 DMARC。

ip4 和 ip6 会计入 SPF 查询上限吗?

不会。ip4、ip6 和 all 不需要 DNS 查询,也不计数。include、a、mx、ptr、exists 和 redirect 各计 1 次,而 include 或 redirect 还会加上其所指向记录中的所有查询。

SPF 的上限是 10 个 include 还是 10 次查询?

是 10 次查询。如果被引用的记录自身又包含 include、a 或 mx 机制,一条只有四个 include 的记录也可能超限。请递归统计每个 include 和 redirect,而不只是看得见的机制。

可以发布第二条 SPF 记录来获得更多查询次数吗?

不可以。同一名称上有两条以 v=spf1 开头的 TXT 记录,结果就是 permerror。请把它们合并成一条,或者把某个发件方迁到独立的子域名,该子域名有自己的记录和独立的 10 次上限。

SPF 展平能解决 10 次查询上限吗?

它能降低计数,但复制来的 IP 地址段会在服务商更改后过期,来自新地址的正常邮件随后会无法通过 SPF。请先删除无用的 include 并使用子域名;只展平地址段稳定且有文档说明的服务商,并定期复查。

如果 SPF 记录返回 permerror,DMARC 一定会失败吗?

不一定。DMARC 只需要一项对齐的通过,所以带有与 From 域名对齐的有效 DKIM 签名的邮件仍能通过。只依赖 SPF 的邮件则不行,这正是修复记录的意义所在。

来源与延伸阅读

  1. RFC 7208:发件人策略框架(SPF)www.rfc-editor.org
  2. RFC 7208 第 4.6.4 节:DNS 查询限制www.rfc-editor.org
  3. RFC 9989:DMARCwww.rfc-editor.org
  4. RFC 8601:用于标示邮件认证状态的邮件头字段www.rfc-editor.org
  5. Microsoft Learn:为 Microsoft 365 域名设置 SPF 以识别有效邮件来源learn.microsoft.com
Sitelemetry 团队

由 Sitelemetry 编辑团队编写。文末的一手资料可帮助你深入了解相关主题。

SITELEMETRY

把学到的知识用起来。

通过 Sitelemetry 探索网站的结构、设置与技术信号。