抓包定位 HTTPS 故障是高级运维 / SRE 的基本功。Wireshark 树状视图覆盖全协议栈,但90% 的握手失败只用看 ClientHello + ServerHello + Alert 三条记录——本工具就是这条速览路径。
TLS 握手能告诉你什么
握手是双方在加密之前的”谈判”——版本、加密算法、密钥、身份证明都在这一段定下来。握手元数据全是明文(除了 ECH 的 inner SNI),所以不需要解密就能看:
- SNI:客户端要访问的域名
- 支持的 TLS 版本:1.0 / 1.1 / 1.2 / 1.3
- 支持的密码套件:100+ 种组合
- 支持的曲线:X25519 / secp256r1 / secp384r1 等
- ALPN 协商:h2 / http/1.1 / h3
- 证书链:服务器身份 + 中间 CA + 根 CA
任意一项谈不拢,握手失败。pcap 里能看到失败发生在哪一步、因为哪个字段——这是排障的钥匙。
TLS 1.2 vs TLS 1.3:握手结构
TLS 1.2(2 RTT)
Client → Server: ClientHello [明文]
Server → Client: ServerHello, Certificate,
ServerKeyExchange, ServerHelloDone [明文]
Client → Server: ClientKeyExchange,
ChangeCipherSpec, Finished [部分加密]
Server → Client: ChangeCipherSpec, Finished [加密]
Client → Server: HTTP GET / ... [加密]
TLS 1.3(1 RTT)
Client → Server: ClientHello (含 key_share) [明文]
Server → Client: ServerHello, EncryptedExtensions,
Certificate, CertificateVerify,
Finished [前两段明文,后续加密]
Client → Server: Finished, HTTP GET / ... [加密]
关键差异——TLS 1.3 把 Certificate 放进了加密部分,所以 pcap 看不到 1.3 的服务器证书内容(除非有 keylog)。1.2 的 Certificate 是明文,工具直接展示 SAN/Issuer/有效期。
五个高频 Alert 速查
抓包里看到 fatal Alert 就是握手失败,alert_description 字段告诉你原因:
| Code | 名字 | 常见根因 |
|---|---|---|
| 40 | handshake_failure | TLS 版本 / cipher / 曲线没共同集 |
| 50 | decode_error | 客户端 / 服务器格式不兼容(罕见,多为协议实现 bug) |
| 70 | protocol_version | TLS 版本被强制拒绝(如服务器禁用 TLS 1.0) |
| 112 | unrecognized_name | SNI 不匹配任何 server_name 配置 |
| 120 | no_application_protocol | ALPN 没共同协议(h2/http/1.1 协商失败) |
详细列表见 RFC 8446 §6。
经典案例:SNI 拼错了
某后台调外部 API 失败,curl 报:
SSL certificate problem: certificate is not yet valid
抓包:
ClientHello SNI = "api.example.cn"
ServerHello selected cipher = TLS_AES_128_GCM_SHA256
Certificate SAN = ["api.example.com", "*.example.com"]
SAN 列表里没 .cn——客户端代码里 BaseURL 配错了 .com 写成 .cn,但 DNS 把 .cn 解析到了同一个 CDN IP。CDN 默认证书签的是 .com,于是 SNI 不匹配证书。
修这个 bug 不需要看一行代码——从 pcap 直接定位是 BaseURL 拼错。
经典案例:HTTP/2 协商失败
App 升级后部分用户报”加载慢一倍”,但服务端日志正常。抓包:
ClientHello alpn_protocols = ["h2", "http/1.1"]
ServerHello selected_protocol = "http/1.1"
服务器没选 h2。继续看证书 + 服务器配置——发现反代链路里 ELB 没开 ALPN passthrough,所以 ALPN 信息被剥掉,源站默认回 http/1.1。
修复:开 ELB 的 TLS passthrough,或者把 ALPN 协商在 ELB 层完成。
经典案例:TLS 1.3 only 卡死老客户端
升级 Nginx 配置 ssl_protocols TLSv1.3 后 30% Android 4.x 用户连不上。抓包看老客户端:
ClientHello supported_versions = ["TLS 1.0", "TLS 1.1", "TLS 1.2"]
Server → Alert (70 protocol_version) fatal
这些设备根本不发 supported_versions 扩展或不带 1.3——服务端拒绝握手。
修复:保留 TLS 1.2 + 1.3,禁用 1.0/1.1 即可,不要全禁 1.2。
用 pcap-viewer 看完整流程
打开 PCAP 抓包查看,拖入 .pcap 后:
- Filter 输入
tls只看 TLS 报文 - 第一条 ClientHello → 看 SNI、versions、ciphers、alpn、groups
- 第二条 ServerHello → 看 selected_version、selected_cipher、selected_alpn
- 后续 Certificate → 看 SAN、Issuer、有效期(仅 TLS 1.2 / 之前明文可见)
- 失败案例查 Alert(红色高亮)→ 看 description code
整条流程不需要看 TCP 重组、不需要追流——握手就在最初几个包里,单包视图就够用。
什么时候必须用 Wireshark
本工具够用的场景占抓包分析的 80%。剩下 20% 必须 Wireshark:
- TCP 流重组:跨多包的大 HTTP body / 大证书链
- 专家系统:自动标 retransmission / out-of-order / window full
- TLS 解密:配 SSLKEYLOGFILE 后看明文 ApplicationData
- 小众协议:BGP / OSPF / 802.11 Radiotap / Bluetooth
- 大文件:> 200 MB 抓包先
tshark -Y过滤再分析 - 统计图表:I/O 图、流量分布、延迟分布
剩下”看 SNI / ALPN / 证书 / Alert / 简单 HTTP”——浏览器拖一下文件就能看的事,没必要装 Wireshark。
抓包命令速查
# macOS - 抓 443 端口流量到指定主机
sudo tcpdump -i en0 -w out.pcap 'host api.example.com and tcp port 443'
# Linux - 抓所有接口
sudo tcpdump -i any -w out.pcap 'tcp port 443'
# 只抓握手部分(节省体积)
sudo tcpdump -i en0 -w handshake.pcap 'tcp port 443 and (tcp[((tcp[12:1] & 0xf0) >> 2):1] = 0x16)'
# tshark 预过滤(脱敏 + 缩小)
tshark -r in.pcap -Y 'tls or dns' -w sanitized.pcap
# iOS(不越狱)
rvictl -s <udid> # 创建虚拟接口
sudo tcpdump -i rvi0 -w ios.pcap 'tcp port 443'
避坑
- 抓包前用
clear清屏——再敲命令,便于对照时间戳 - 加
-s 0抓全包——默认只截 96 字节够 IP/TCP 头但 TLS 握手会被截断 - 复现请求前再开始抓——边抓边操作,避免无关流量混进来
- Ctrl+C 后看一眼包数——
xxx packets captured太多说明过滤条件没生效
抓包不是黑魔法——TLS 握手协议设计就是让你能看见。装好 tcpdump,记住几个 Alert 编号,浏览器拖一下文件,HTTPS 故障定位从”翻日志四小时”变成”看 pcap 三分钟”。