强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

SMTP 服务器搭建完全指南 / 第 8 章:SPF 发件人策略框架

第 8 章:SPF 发件人策略框架

SPF 告诉全世界:“只有这些服务器有权代表我的域名发送邮件。”


8.1 SPF 概述

8.1.1 什么是 SPF

SPF(Sender Policy Framework)是一种邮件认证机制,允许域名所有者通过 DNS TXT 记录声明哪些 IP 地址/服务器有权代表该域名发送邮件。

工作原理

1. 发送方在 DNS 中发布 SPF 记录
   example.com TXT "v=spf1 mx a ip4:203.0.113.10 -all"

2. 接收方收到邮件后,查询发件域名的 SPF 记录

3. 接收方检查发送服务器 IP 是否在 SPF 记录中

4. 根据检查结果返回:
   • PASS    — IP 被授权
   • FAIL    — IP 未被授权
   • SOFTFAIL — IP 可能未被授权(建议)
   • NEUTRAL — 不确定
   • NONE    — 无 SPF 记录

8.1.2 SPF 的作用

作用说明
防止域名欺骗阻止冒用你的域名发送邮件
提高送达率通过 SPF 验证的邮件更少被拒收
减少垃圾邮件帮助接收方识别伪造邮件
配合 DMARCSPF 是 DMARC 策略的基础之一

8.1.3 SPF 的局限性

局限说明
仅验证发件 IP不能验证邮件内容
转发问题邮件转发后 SPF 验证可能失败
需要配合 DKIMSPF 单独使用效果有限
查询限制DNS 查询不能超过 10 次

8.2 SPF 记录语法

8.2.1 SPF 记录结构

v=spf1 <机制1> <机制2> ... <限定词>

基本格式

v=spf1 [指令] [指令] ... [默认策略]

8.2.2 限定词(Qualifier)

限定词含义返回结果说明
+PassPASS允许(默认)
-FailFAIL拒绝
~SoftFailSOFTFAIL软拒绝(建议标记为垃圾)
?NeutralNEUTRAL中立(不做判断)

8.2.3 机制(Mechanism)

机制说明示例
all匹配所有(通常放在最后)-all
ip4匹配 IPv4 地址/范围ip4:203.0.113.10
ip6匹配 IPv6 地址/范围ip6:2001:db8::/32
a匹配域名的 A 记录a
mx匹配域名的 MX 记录mx
include引用其他域名的 SPF 记录include:_spf.google.com
exists检查域名是否存在 A 记录exists:%{i}._spf.example.com
redirect重定向到其他域名的 SPFredirect=_spf.example.com

8.2.4 修饰符(Modifier)

修饰符说明示例
include引用外部 SPFinclude:_spf.google.com
redirect重定向redirect=_spf.example.com
exp自定义解释exp=explain._spf.example.com

8.3 配置 SPF 记录

8.3.1 基本 SPF 记录

; 允许域名的 MX 记录中的服务器发送
example.com. IN TXT "v=spf1 mx -all"

; 允许域名的 A 记录中的服务器发送
example.com. IN TXT "v=spf1 a -all"

; 允许 MX 和 A 记录中的服务器发送
example.com. IN TXT "v=spf1 mx a -all"

8.3.2 常见配置模板

场景 1:单服务器

; 只允许一台服务器发送
example.com. IN TXT "v=spf1 ip4:203.0.113.10 -all"

场景 2:多服务器

; 允许多台服务器发送
example.com. IN TXT "v=spf1 ip4:203.0.113.10 ip4:203.0.113.11 ip4:203.0.113.12 -all"

场景 3:包含第三方服务

; 允许自己的服务器和 Google Workspace
example.com. IN TXT "v=spf1 mx a include:_spf.google.com -all"

场景 4:复杂配置

; 允许 MX、特定 IP、第三方服务
example.com. IN TXT "v=spf1 mx ip4:203.0.113.0/24 include:_spf.google.com include:spf.protection.outlook.com -all"

8.3.3 第三方服务 SPF 记录

服务SPF include
Google Workspaceinclude:_spf.google.com
Microsoft 365include:spf.protection.outlook.com
Amazon SESinclude:amazonses.com
SendGridinclude:sendgrid.net
Mailchimpinclude:servers.mcsv.net
Zoho Mailinclude:zoho.com
阿里企业邮include:spf.mxhichina.com
腾讯企业邮include:spf.mail.qq.com

8.3.4 SPF 记录查询限制

SPF 记录中的 includemxa 等机制会产生 DNS 查询。RFC 7208 规定:

限制说明
最大查询次数10 次
最大 void 查询2 次
TXT 记录长度建议不超过 255 字符

查询次数计算

每个机制的 DNS 查询次数:
- ip4/ip6: 0 次(无 DNS 查询)
- a: 1 次
- mx: 2 次(MX + A)
- include: 1+ 次(递归计算)
- exists: 1 次

8.4 使用 spf-tools 工具

8.4.1 安装工具

# 安装 Python SPF 测试工具
pip install pyspf pydns

# 或使用在线工具
# https://www.kitterman.com/spf/validate.html

8.4.2 命令行验证 SPF

# 使用 dig 查询 SPF 记录
dig TXT example.com +short

# 使用 nslookup
nslookup -type=TXT example.com

# 使用 Python 验证 SPF
python3 -c "
import spf
result, explanation = spf.check2(i='203.0.113.10', s='[email protected]', h='mail.example.com')
print(f'SPF 结果: {result}')
print(f'解释: {explanation}')
"

8.4.3 SPF 验证脚本

#!/bin/bash
# spf-check.sh — SPF 记录检查脚本

DOMAIN="$1"

if [ -z "$DOMAIN" ]; then
    echo "用法: $0 <域名>"
    exit 1
fi

echo "=== SPF 记录检查: $DOMAIN ==="

# 1. 查询 SPF 记录
echo "[1/4] 查询 SPF TXT 记录..."
SPF_RECORD=$(dig +short TXT $DOMAIN | grep "v=spf1")

if [ -z "$SPF_RECORD" ]; then
    echo "  ❌ 未找到 SPF 记录"
    exit 1
else
    echo "  ✅ SPF 记录: $SPF_RECORD"
fi

# 2. 检查 SPF 版本
echo "[2/4] 检查 SPF 版本..."
if echo "$SPF_RECORD" | grep -q "^v=spf1"; then
    echo "  ✅ SPF 版本正确: v=spf1"
else
    echo "  ❌ SPF 版本格式错误"
fi

# 3. 检查默认策略
echo "[3/4] 检查默认策略..."
if echo "$SPF_RECORD" | grep -q "\-all"; then
    echo "  ✅ 默认策略: -all (Fail,推荐)"
elif echo "$SPF_RECORD" | grep -q "~all"; then
    echo "  ⚠️ 默认策略: ~all (SoftFail,建议使用 -all)"
elif echo "$SPF_RECORD" | grep -q "?all"; then
    echo "  ⚠️ 默认策略: ?all (Neutral,不推荐)"
elif echo "$SPF_RECORD" | grep -q "+all"; then
    echo "  ❌ 默认策略: +all (Pass,危险!)"
else
    echo "  ⚠️ 未找到默认策略"
fi

# 4. 检查 DNS 查询次数
echo "[4/4] 估算 DNS 查询次数..."
QUERY_COUNT=0
for mech in include mx a; do
    COUNT=$(echo "$SPF_RECORD" | grep -o "$mech" | wc -l)
    if [ "$mech" = "mx" ]; then
        QUERY_COUNT=$((QUERY_COUNT + COUNT * 2))
    else
        QUERY_COUNT=$((QUERY_COUNT + COUNT))
    fi
done
echo "  估算查询次数: $QUERY_COUNT / 10"

if [ $QUERY_COUNT -gt 10 ]; then
    echo "  ❌ 超过 SPF 查询限制(10 次)"
else
    echo "  ✅ 查询次数在限制内"
fi

echo ""
echo "=== 检查完成 ==="

8.5 SPF 与邮件转发

8.8.1 转发问题

SPF 转发问题示意:

发送方 (IP: 1.1.1.1)
    │
    ▼
转发服务器 (IP: 2.2.2.2)  ← SPF 检查失败!
    │                       因为 2.2.2.2 不在发送方的 SPF 记录中
    ▼
接收方

解决方案:
1. 使用 SRS (Sender Rewriting Scheme)
2. 依赖 DKIM 签名(转发后仍有效)
3. 使用 DMARC 策略(综合 SPF 和 DKIM)

8.5.2 SRS 配置

# 安装 postsrsd
sudo apt install -y postsrsd

# 配置 Postfix
sudo postconf -e "sender_canonical_maps = tcp:127.0.0.1:10001"
sudo postconf -e "sender_canonical_classes = envelope_sender"
sudo postconf -e "recipient_canonical_maps = tcp:127.0.0.1:10002"
sudo postconf -e "recipient_canonical_classes = envelope_recipient"

# 启动 postsrsd
sudo systemctl enable --now postsrsd

# 重新加载 Postfix
sudo systemctl reload postfix

8.6 SPF 验证结果处理

8.6.1 Postfix 中的 SPF 检查

# 安装 SPF 检查工具
sudo apt install -y postfix-policyd-spf-python

# /etc/postfix/main.cf
smtpd_recipient_restrictions =
    permit_mynetworks,
    permit_sasl_authenticated,
    reject_unauth_destination,
    check_policy_service unix:private/policyd-spf,
    reject_rbl_client zen.spamhaus.org

# /etc/postfix/master.cf — 添加 SPF 策略服务
policyd-spf  unix  -       n       n       -       0       spawn
    user=policyd-spf argv=/usr/bin/policyd-spf

8.6.2 SPF 结果与 DMARC

SPF 验证结果对 DMARC 的影响:

SPF PASS + 对齐检查通过 → DMARC PASS
SPF PASS + 对齐检查失败 → DMARC 可能失败(取决于 DKIM)
SPF FAIL + 对齐检查失败 → DMARC FAIL(除非 DKIM PASS)
SPF SOFTFAIL             → DMARC FAIL(除非 DKIM PASS)

注意:SPF 验证和 DMARC 对齐是两回事!
- SPF 验证:检查发送 IP 是否被授权
- DMARC 对齐:检查 MAIL FROM 域名与 From 头域名是否匹配

8.7 业务场景:多服务商 SPF 配置

场景描述

一家企业使用以下服务发送邮件:

  • 自建邮件服务器 (203.0.113.10)
  • Google Workspace
  • Microsoft 365
  • SendGrid 营销邮件
  • 客服系统 Zendesk

SPF 配置

; 企业 SPF 记录
example.com. IN TXT "v=spf1 \
    ip4:203.0.113.10/32 \
    include:_spf.google.com \
    include:spf.protection.outlook.com \
    include:sendgrid.net \
    include:mail.zendesk.com \
    -all"

DNS 查询次数计算

机制查询次数
ip4:203.0.113.10/320
include:_spf.google.com1 (+递归)
include:spf.protection.outlook.com1 (+递归)
include:sendgrid.net1
include:mail.zendesk.com1
总计约 8 次

8.8 常见问题排查

问题 1:SPF 验证失败

# 检查 SPF 记录
dig TXT example.com +short

# 检查发送服务器 IP
curl -s ifconfig.me

# 验证 IP 是否在 SPF 记录中
# 使用在线工具: https://www.kitterman.com/spf/validate.html

问题 2:DNS 查询超限

症状:邮件日志中出现 "too many DNS lookups"

解决方案:
1. 减少 include 数量
2. 使用 ip4/ip6 代替 include(不产生 DNS 查询)
3. 使用 SPF 扁平化服务
   - https://dmarcian.com/spf-survey/
   - https://www.spftools.com/

问题 3:邮件转发后 SPF 失败

症状:转发的邮件被标记为垃圾邮件

解决方案:
1. 配置 SRS(Sender Rewriting Scheme)
2. 确保 DKIM 签名有效(DKIM 不受转发影响)
3. 使用 DMARC 策略(SPF 或 DKIM 任一通过即可)

8.9 注意事项

⚠️ SPF 记录数量限制

  • 每个域名只能有一条 SPF TXT 记录
  • 多条 SPF 记录会导致验证失败
  • 如需合并多个 SPF 记录,使用 includeip4/ip6

⚠️ SPF 查询限制

  • 总 DNS 查询次数不超过 10 次
  • 超过限制会导致验证失败(PermError)
  • 使用在线工具检查查询次数

💡 SPF 最佳实践

  • 使用 -all 而不是 ~all
  • 定期审查 SPF 记录,移除不再使用的服务
  • 优先使用 ip4/ip6(不产生 DNS 查询)
  • 配合 DKIM 和 DMARC 使用

8.10 扩展阅读


上一章← 第 7 章:DKIM 邮件签名 下一章第 9 章:DMARC 策略与报告 →