本文是《计算机网络学习笔记》系列的第十四篇。每天有数千亿封邮件在互联网上流转,但大多数人并不知道邮件是怎么从发件方的邮件客户端,一路送达收件方的邮箱的。本文拆解 SMTP 协议的对话格式,讲清楚一封邮件发出去的完整过程,以及为什么现在抓包已经看不到明文内容了。

一、邮件系统的三个角色

在深入 SMTP 之前,先理清邮件系统中的三个角色:

发件人                                          收件人
  │                                               │
  ▼                                               ▼
邮件客户端                                    邮件客户端
(Outlook/Thunderbird)                        (Outlook/Thunderbird)
  │  SMTP(发送)                                  ▲
  ▼                                               │  IMAP/POP3(拉取)
发件方邮件服务器  ──── SMTP(中转)────►  收件方邮件服务器
(smtp.example.com)                         (smtp.test.com)
  • MUA(Mail User Agent,邮件用户代理):就是你用的邮件客户端,比如 Outlook、Thunderbird,负责撰写和接收邮件;
  • MTA(Mail Transfer Agent,邮件传输代理):邮件服务器,负责在服务器之间转发邮件;
  • SMTP:负责推送邮件,用于 MUA → MTA,以及 MTA → MTA 之间的传输;
  • IMAP / POP3:负责拉取邮件,用于 MUA 从 MTA 收取邮件。
简单记:SMTP 管发,IMAP/POP3 管收。

二、SMTP 协议基础

SMTP(Simple Mail Transfer Protocol,简单邮件传输协议)是一个基于文本的协议,运行在 TCP 之上,使用 25 端口(加密版使用 587 或 465 端口)。

所谓"基于文本",意味着客户端和服务端之间交换的是人类可读的 ASCII 字符串,每条命令或响应以 \r\n 结尾——这和 HTTP/1.1 的设计思路完全一致。

SMTP 的每条响应都以一个三位数字状态码开头:

  • 2xx:成功
  • 3xx:需要继续操作
  • 4xx:临时错误(可以重试)
  • 5xx:永久错误(不要重试)

三、完整的 SMTP 对话

一次完整的 SMTP 会话分为三个阶段:建立连接、传输信封、传输内容。

# ── 建立连接 ──────────────────────────────────────────────
S: 220 smtp.example.com Service ready       # 服务器就绪
C: EHLO client.example.com                  # 客户端打招呼,声明自己身份
S: 250-smtp.example.com Hello               # 服务器回应,250 表示成功
S: 250-SIZE 35882577                        # 顺便告知最大邮件大小
S: 250 STARTTLS                             # 告知支持的扩展能力

# ── 传输信封(Envelope)──────────────────────────────────
C: MAIL FROM: <[email protected]>          # 声明发件人
S: 250 OK

C: RCPT TO: <[email protected]>             # 声明收件人(可多次,支持多个收件人)
S: 250 OK

# ── 传输内容(Message)──────────────────────────────────
C: DATA                                     # 请求开始发送邮件内容
S: 354 Start mail input; end with <CRLF>.<CRLF>  # 服务器同意,告知结束符

C: Subject: Hello
C: From: [email protected]
C: To: [email protected]
C:
C: This is the email body.
C: .                                        # 关键:单独一行的句号,表示内容结束
S: 250 OK                                   # 服务器确认接收完毕

# ── 断开连接 ──────────────────────────────────────────────
C: QUIT
S: 221 Service closing channel

几个值得注意的细节

HELO vs EHLO

HELO 是原始 SMTP 的打招呼命令;EHLO(Extended HELO)是扩展版,服务器在回应中会列出自己支持的所有扩展能力(如 STARTTLSAUTH、最大邮件大小等)。现代客户端都应该用 EHLO

信封(Envelope)与邮件头(Header)是两回事

SMTP 的 MAIL FROMRCPT TO信封,决定邮件实际送往哪里;DATA 里面的 From:To:邮件头,是你在邮件客户端里看到的显示信息。两者可以不一样——这也是垃圾邮件伪造发件人的原理。

单独一行的句号是结束符

DATA 阶段,邮件正文以"单独一行,只有一个句号"(\r\n.\r\n)作为结束标志。如果正文本身某行恰好只有一个句号,协议规定必须转义成两个句号(.),接收方再还原回来。


四、邮件现状:为什么抓包看不到明文?

现代邮件传输几乎全部在 SMTP 之外套上了 TLS 加密层,有两种方式:

方式端口机制
STARTTLS587先建立普通 TCP 连接,然后通过 STARTTLS 命令升级为加密连接
SMTPS465从一开始就建立 TLS 连接,相当于 SMTP over TLS

因为有了 TLS,即使用 Wireshark 抓包,看到的也是一堆密文乱码,无法直接读取邮件内容。

如果想看到明文的 SMTP 对话,可以在本地搭一个不加密的测试环境:

# 使用 Python 在本地启动一个 SMTP 调试服务器(接收到的邮件直接打印到终端)
python -m smtpd -n -c DebuggingServer localhost:1025

# 新开一个终端,用 telnet 模拟客户端
telnet localhost 1025

# 然后按照上面的对话格式,从 EHLO 开始逐行输入
注意:Windows 下 telnet 的退格键行为异常,输错了无法删除,建议直接重新连接。

五、收邮件:IMAP vs POP3

SMTP 只负责发送,收件用的是另外两个协议:

IMAPPOP3
全称Internet Message Access ProtocolPost Office Protocol 3
端口143(993 加密版)110(995 加密版)
工作方式邮件保留在服务器,客户端只是"查看"邮件下载到本地,默认从服务器删除
多设备支持✅ 手机、电脑都能看到同一份邮件❌ 在一台设备下载后,其他设备看不到
离线访问需要同步,取决于客户端实现下载后完全离线可用
现状主流,Gmail/Outlook 均支持已基本淘汰

POP3 之所以被淘汰,核心原因是:它把邮件下载到本地后就从服务器删除,换一台设备就看不到历史邮件了。在多设备成为常态的今天,这个设计无法接受。


六、DNS 与邮件:MX 记录

发件方的邮件服务器怎么知道,要把邮件发给哪台服务器?

答案是 DNS 的 MX 记录(Mail Exchanger)

# 查询 gmail.com 的邮件服务器
nslookup -type=MX gmail.com

# 输出示例:
# gmail.com  MX preference = 5,  mail exchanger = gmail-smtp-in.l.google.com
# gmail.com  MX preference = 10, mail exchanger = alt1.gmail-smtp-in.l.google.com

当你发一封邮件到 [email protected],发件方服务器会先查询 gmail.com 的 MX 记录,拿到目标邮件服务器的域名,再解析其 IP,最后用 SMTP 把邮件投递过去。

MX 记录可以有多条,preference 值越小优先级越高,实现了负载均衡和故障转移


小结

概念一句话总结
SMTP基于文本的邮件发送协议,TCP 25/587/465 端口
EHLO扩展握手命令,服务器回应时列出支持的所有能力
信封 vs 邮件头MAIL FROM/RCPT TO 决定实际投递路径,From:/To: 只是显示信息
STARTTLS / SMTPS现代 SMTP 的加密方式,抓包只能看到密文
IMAP邮件留在服务器,多设备同步,已取代 POP3 成为主流
MX 记录DNS 记录类型,指定某个域名的邮件应投递到哪台服务器

本系列前十三篇:
· 第1篇:《TCP 协议格式详解》
· 第2篇:《TCP 三次握手与四次挥手》
· 第3篇:《TCP 可靠数据传输》
· 第4篇:《TCP 流量控制与拥塞控制》
· 第5篇:《TLS:HTTPS 背后的加密握手》
· 第6篇:《HTTP 协议进化史》
· 第7篇:《DNS:互联网的电话簿》
· 第8篇:《IPv4 与 IPv6:网络层的寻址与路由》
· 第9篇:《NAT:家里的路由器如何帮你"上网"》
· 第10篇:《UDP 协议详解》
· 第11篇:《ICMP:ping 和 traceroute 背后的协议》
· 第12篇:《链路层:以太网帧与 ARP 协议》
· 第13篇:《从输入 URL 到页面显示:全流程串讲》


参考资料:《计算机网络:自顶向下方法》

标签: none

添加新评论