一次网络请求的完整旅程
当请求失败时,“网络有问题”的范围太大,无法指导排查。更快的方法是确定最后一个明确成功的阶段,以及第一个没有成功的阶段。
URL 之外的路径
text
应用程序
→ 名称解析
→ 本地路由或 TUN 设备
→ 可选代理
→ TCP 连接
→ TLS 握手
→ HTTP 交换
→ 反向代理
→ 应用服务
→ 数据库或下游服务页面上的主机名不一定是真实连接目标。代理可能接收主机名并负责建立远程连接;TUN 客户端也可能返回合成地址,在操作系统真正访问公网前拦截流量。
1. 名称解析
记录解析结果以及由谁生成。普通公网地址、私网地址和 Fake-IP 网段中的合成地址代表不同路径。
需要回答:
- 应用使用系统 Resolver、DNS-over-HTTPS,还是代理的 Resolver?
- 解析结果是否来自缓存?
- 代理收到的是主机名,还是已经解析的地址?
修改 HTTPS_PROXY 等环境变量并不能改变 TUN 路由或 DNS 拦截规则。
2. 路由和代理选择
connect() 成功只说明有组件接受了 Socket。存在本地隧道时,这个组件可能是隧道进程,而不是远程服务器。
应当同时收集两类信息:
- 操作系统选择的路由和网络接口;
- 代理或隧道的连接记录与命中规则。
如果策略要求仅对一个目标直连,必须验证运行时规则命中。只修改配置文件而不确认运行进程已经重新加载,并不能证明路径已经改变。
3. TCP 连接
TCP 只建立字节流,不负责认证服务器,也不能证明预期协议正在响应。
nc -vz host 5432 可能通过拦截代理报告成功,即使服务器完全没有 PostgreSQL Listener。应发送协议握手,或直接检查服务器监听端口,才能判断数据库是否暴露。
4. TLS 握手
TLS 包含三个独立结论:
- 成功协商加密会话;
- 证书链受信任;
- 证书身份与主机名或 IP 匹配。
关闭校验会隐藏证据并引入新的安全问题。正确做法是检查证书、SNI、信任库和具体握手错误。
5. HTTP 与应用语义
HTTP 响应只证明有一个支持 HTTP 的组件进行了回复。它可能是代理错误页、反向代理兜底页面,也可能是目标 API。
至少验证:
- 状态码;
- Content-Type 和稳定的响应字段;
- 可用时检查 Request ID 或服务端身份;
- 业务相关的数量和边界条件。
对于数据 API,HTTP 200 是验证的开始,而不是结束。
证据表
| 阶段 | 有力证据 | 尚不能证明 |
|---|---|---|
| DNS | Resolver 与返回地址 | 实际使用的路由 |
| 路由 | 网络接口与代理命中规则 | 远程协议已接受请求 |
| TCP | 完成三次握手 | 服务器身份 |
| TLS | 证书验证与加密会话 | 应用行为正确 |
| HTTP | 预期状态和 Schema | 数据完整 |
| 应用 | 与源数据比较边界条件 | 其他接口也正常 |
排查顺序
- 记录完整命令、URL 和时间。
- 检查阻塞进程及其当前连接目标。
- 验证解析结果和路由选择。
- 测试真实协议,而不只测试 TCP 可达性。
- 对照客户端证据、服务端监听和日志。
- 每次只改变一个作用域明确的变量,然后重复同一测试。
这个顺序可以把模糊的网络问题缩小为一个明确的阶段失败。