12.1 从症状到证据:分层网络诊断
网络故障很少会主动告诉你自己在哪一层。浏览器里的“打不开”、客户端里的“超时”,都只是结果。有效的排障不是轮流运行一堆命令,而是先提出一个能被证伪的假设,再收集恰好足够的证据。
本课目标
完成本课后,你应当能够:
- 把模糊症状改写成包含范围、时间和阶段的故障描述;
- 分清 DNS、路由、TCP、TLS 和 HTTP 各阶段的证据边界;
- 正确解释
ping、traceroute、ss、dig与curl的输出; - 用对照实验缩小故障范围,而不是凭一条命令直接归因。
先写清故障边界
在终端里输入命令之前,先回答五个问题:
- 谁失败:一个用户、一台主机、一个可用区,还是所有调用方?
- 访问什么失败:一个域名、一组 IP、一个端口,还是整个网络?
- 从何时开始:持续失败、间歇失败,还是只在峰值出现?
- 失败在哪个阶段:解析、连接、TLS、首字节,还是传输过程中?
- 最近有什么变化:DNS、证书、路由、防火墙、代理、发布或容量?
“网站很慢”无法指导行动。“北京办公网访问 api.example.test 时,DNS 在 20 ms 内完成,但 TLS 握手从 40 ms 升到 3 s;手机网络正常”就可以产生多个可检验假设。
分层不是固定命令顺序
分层诊断的价值在于隔离变量,不是要求每次都从网线查起。若监控已证明 DNS 解析成功,就应直接利用这份证据;若只有一台主机失败,则先比较这台主机与正常主机的配置。
| 阶段 | 核心问题 | 常用证据 |
|---|---|---|
| 本机与链路 | 接口、地址、默认路由是否存在 | ip link、ip address、ip route |
| 名称解析 | 应用实际得到哪些地址 | getent ahosts、resolvectl、dig |
| 路径 | 内核会从哪个接口、源地址、下一跳发送 | ip route get、ping、traceroute、mtr |
| 传输 | 是否监听,SYN 后发生了什么 | ss、nc、抓包 |
| TLS | SNI、证书链、时间、ALPN 是否匹配 | openssl s_client、curl -v |
| HTTP | 状态码、重定向、首字节和响应体是否正常 | curl、服务端日志、trace ID |
第一步:检查本机路径
以下 203.0.113.10 是文档示例地址,不是可用的测试服务器;执行探测前替换为本次诊断的授权目标。Linux 上可以先查看接口、地址和路由选择:
ip -brief link
ip -brief address
ip route show
ip route get 203.0.113.10ip route get 显示内核对这个目标当前会采用的路由决策,通常包括输出接口、下一跳和源地址。它不证明远端可达,也不表示数据包真的已经发出。
若应用运行在容器或 network namespace 中,应在应用所在的网络空间执行检查。宿主机有默认路由,不代表容器也有。
第二步:核对应用实际使用的名称解析
dig 直接查询 DNS,适合观察记录和权威链;但应用通常通过系统解析器工作,还可能受 /etc/hosts、NSS、systemd-resolved、搜索域和缓存影响。因此先问“应用得到什么”,再问“DNS 服务器回答什么”。getent 反映系统名称服务路径,不能复现浏览器 DoH、自带解析器或应用内缓存。
# 经过系统名称服务配置,较接近普通应用的解析路径
getent ahosts example.com
# 查看 DNS 回答及 TTL
dig example.com A
dig example.com AAAA
# 与指定递归解析器做对照
dig @1.1.1.1 example.com A
# 从根区开始迭代查询;这是 dig 自己执行的追踪,不是复现应用的递归服务器缓存路径
dig +trace example.com重点比较:
- 返回码是
NOERROR、NXDOMAIN还是SERVFAIL; NOERROR是否伴随空答案;结合 authority 中的 SOA/NS 区分无此类型记录的 NODATA 与 referral,不能见到空 answer 就认定是负回答;- A 与 AAAA 是否都存在,应用是否优先尝试 IPv6;
- CNAME 链最终是否得到地址;
- 不同解析器的回答和 TTL 是否不同;
- DNSSEC 验证失败、分区视图和负缓存是否参与其中。
只执行 dig +short 会丢掉返回码、TTL 和 authority 等关键证据,不适合用作完整诊断。
第三步:解释 ICMP 与路径探测
ping 能证明什么
ping -c 5 203.0.113.10收到 Echo Reply 能证明:在测试时刻,这个源和目标之间存在一条允许相应 ICMP 报文往返的路径。它不能证明 TCP 443 正常,也不能证明应用健康。
没有 Echo Reply 的原因可能包括目标不可达、路径丢包、ICMP 被过滤或限速、目标禁用响应。因而“ping 不通”不能单独推出“网络断了”。
RTT 是往返时间,包含正向路径、目标处理和反向路径。互联网路由可能不对称,某一跳的 RTT 增大也不等于那条正向链路就是瓶颈。
traceroute 与 mtr 的边界
traceroute 逐步增加 TTL/Hop Limit,并利用路由器返回的 ICMP Time Exceeded 观察一组可能的中间节点:
traceroute -n 203.0.113.10
mtr --report --report-cycles 20 --no-dns 203.0.113.10解释结果时遵守三条规则:
- 中间一跳显示
*,但后续跳和目标正常,通常只说明该节点没有回复探测或对回复限速。 - 中间一跳显示高丢包,而后续跳不丢,不能把它判为转发丢包;路由器可能优先转发业务包、低优先级处理发给自己的 ICMP。
- ECMP、负载均衡和路径变化可能让每次探测走不同路径。显示出来的是探测证据,不是完整拓扑。
只有当异常从某一跳开始并持续到后续节点和目标,而且与业务流量的协议、源地址和目的端口足够相似时,才更值得怀疑该位置附近的路径。
第四步:区分监听、拒绝与超时
在服务端查看监听 socket:
ss -lntp
ss -lnt 'sport = :443'ss -lntp 只能说明当前 network namespace 中有进程监听某个本地地址和端口。监听 127.0.0.1:443 不等于外部接口可访问;容器内监听也不等于宿主机已经发布端口。
从客户端测试 TCP 连接:
nc -vz -w 3 example.com 443常见现象:
| 现象 | 直接证据 | 可能原因 |
|---|---|---|
很快 Connection refused | 常见情况是收到 RST 或本机直接拒绝 | 没有监听、端口映射错误、设备主动 reject |
| 连接阶段超时 | 在期限内未建立连接 | 静默丢弃、路由问题、回程问题、对端无响应、SYN 队列或资源压力 |
| 连接成功但读取超时 | TCP 已建立,应用数据未按时到达 | 服务处理慢、上游慢、协议等待、响应被中途丢弃 |
这些是候选解释,不是单凭错误字符串就能确定的根因。要区分它们,应结合客户端抓包、服务端监听状态、防火墙计数器和服务端日志。
第五步:把 TLS 和 HTTP 分开
用 curl 同时记录各阶段耗时:
curl --silent --show-error --output /dev/null \
--connect-timeout 3 --max-time 10 \
--write-out $'remote_ip=%{remote_ip}\nhttp_code=%{http_code}\nnamelookup=%{time_namelookup}\nconnect=%{time_connect}\nappconnect=%{time_appconnect}\nstarttransfer=%{time_starttransfer}\ntotal=%{time_total}\n' \
https://example.com/time_namelookup:从开始到名称解析完成;time_connect:从开始到传输连接建立;time_appconnect:从开始到 TLS 等应用层握手完成;time_starttransfer:从开始到收到第一个响应字节;time_total:整个传输完成。
--connect-timeout 覆盖连接阶段的 DNS、传输及 TLS 等握手,并非只给 TCP 计时;--max-time 限制这次传输总时长。这里没有启用 --fail,HTTP 404/500 仍可能让 curl 以 0 退出,因此必须同时检查状态码与业务响应。
这些是累计时间。某阶段的近似耗时要用相邻时间点相减,而且连接复用、代理、HTTP/3 和版本差异会改变阶段含义。记录 curl --version 与实际协商协议,避免把公式当成跨环境不变的真相。
绕过 DNS,但保留 Host 与 SNI
curl --noproxy '*' --connect-timeout 3 --max-time 10 \
--resolve example.com:443:203.0.113.10 https://example.com/本例用 --noproxy '*' 明确测试直连后端,避免代理接管解析;它不复现必须经代理的业务路径。--resolve 为指定 host:port 提供地址,同时 URL 中仍是原域名,因此适合测试某个后端地址并保留 HTTP Host 和 TLS SNI。直接请求 https://203.0.113.10/ 往往会改变虚拟主机选择和证书校验,不能作为等价对照。
检查 TLS
openssl s_client \
-connect example.com:443 \
-servername example.com \
-verify_hostname example.com -verify_return_error \
-alpn h2,http/1.1 \
-showcerts </dev/null-verify_return_error 让证书验证错误终止握手;否则 s_client 默认可能显示错误后继续,进程成功退出也不等于验证通过。-alpn 提出协议列表,再查看服务端选择。检查证书是否在有效期内、主机名是否匹配、服务端是否发送了必要的中间证书,以及最终 verify return code。-servername 明确设置 SNI;没有它,多租户服务可能返回另一个站点的证书。
curl -k 或 --insecure 只能用于判断“失败是否与证书验证有关”,不能作为修复方案,更不能进入生产脚本。
一张可执行的证据表
| 已知事实 | 下一步对照 | 暂时不要下的结论 |
|---|---|---|
| 系统解析器没有返回地址 | 比较 getent、dig、指定解析器与本机配置 | “权威 DNS 一定挂了” |
ping 成功,443 超时 | 用 nc/curl 测端口,双端抓 SYN | “服务器正常” |
| TCP 成功,TLS 失败 | 核对 SNI、时间、证书链、协议版本 | 误判为应用代码缺陷 |
| TLS 成功,首字节很慢 | 查服务端 trace、上游依赖和排队 | “带宽不足” |
mtr 中间节点丢包,目标不丢 | 延长观测并比较目标与业务指标 | “该路由器丢了 30% 业务包” |
本课小结
网络诊断的基本单位不是命令,而是“假设:实验:证据:结论”。一条输出通常只能排除一小组可能性。先确定失败范围和阶段,再设计保留关键变量的对照,才能把“偶尔打不开”逐步收敛成可交付的根因证据。
下一课将进入抓包:如何选择观测点、编写过滤器,并避免把网卡卸载或分析器提示误当成线上事实。
规范与手册入口
- Linux
ip-route(8)、ss(8)手册; - BIND 9 Administrator Reference Manual:
dig; - curl 命令手册;
- OpenSSL
s_clientmanual; - RFC 792(ICMPv4)、RFC 4443(ICMPv6)。