KernelRanch
返回文章列表

网络连通性排查顺序:DNS、TCP、TLS、HTTP 每层一条命令,四条报错对四个层

2026-11-10 #运维

"连不上"是四件不同的事:名字解析不出来、端口不通、TLS 握手失败、HTTP 返回错误码。按 DNS → TCP → TLS → HTTP 的顺序每层打一条命令,任何一层失败就停在那层,不要往下猜。这个顺序的好处是每层只依赖上一层的结果,不会出现"DNS 错了却在查证书"这种浪费时间的情况。

四层各一条命令

层一条命令通过判据失败时的典型报错
DNSgetent hosts example.com 或 dig +short A example.com返回至少一个 A/AAAA 记录Could not resolve host / status: NXDOMAIN
TCPnc -z -w3 example.com 443 或 timeout 5 bash -c "cat < /dev/null > /dev/tcp/example.com/443"无输出且退出码 0Connection refused / Connection timed out
TLSopenssl s_client -connect example.com:443 -servername example.com -briefCONNECTION ESTABLISHED + Verification: OKwrong version number / self-signed certificate
HTTPcurl -sS -o /dev/null -w '%{http_code}\n' https://example.com/期望的状态码404/500 等(连通性没问题,是应用层)

一条命令看完整链路耗时,比逐层手打更快:

curl -sS -o /dev/null \
  -w 'code=%{http_code} dns=%{time_namelookup}s tcp=%{time_connect}s tls=%{time_appconnect}s total=%{time_total}s\n' \
  --max-time 15 https://example.com/

实测输出:

code=200 dns=0.001446s tcp=0.002181s tls=1.118325s total=1.756965s

四个数字正好对应四层:dns 是名字解析,tcp 是 TCP 握手完成(相对开始),tls 是 TLS 握手完成,total 是整次请求。这一次 TLS 用了 1.118 秒,占 1.757 秒总耗时的 64%,而 DNS 和 TCP 都在 2 毫秒级别——"慢"发生在 TLS 层,不在网络上。

四层失败各打一次

$ curl -sS --max-time 8 http://no-such-host-kernelranch.invalid/
curl: (6) Could not resolve host: no-such-host-kernelranch.invalid

$ curl -sS --max-time 8 http://127.0.0.1:9/
curl: (7) Failed to connect to 127.0.0.1 port 9 after 0 ms: Couldn't connect to server

$ curl -sS --max-time 8 https://example.com:80/
curl: (35) OpenSSL/3.0.22: error:0A00010B:SSL routines::wrong version number

三个退出码就是三条定位结论:6 是 DNS,7 是 TCP,35 是 TLS。curl 的退出码比任何日志都直接,脚本里判断 $? 就能分流。HTTP 层不产生 curl 错误码(除非 --fail),它把状态码原样返回,404 和 500 都算"连通成功"。

四个坑

第一,用 ping 判断"通不通"。很多主机禁 ICMP,ping 不通但 443 正常;反过来 ping 通也不代表端口开着。判断端口用 nc -z 或 /dev/tcp,不要用 ping。

第二,DNS 有多条 A 记录时的"时通时不通"。本次 getent hosts example.com 返回两个地址(172.66.147.243、104.20.23.154),客户端可能只连其中一个。这种情况用 curl --resolve 固定到某台,能立刻判断是不是某一台有问题:

curl -sv --resolve example.com:443:172.66.147.243 https://example.com/

第三,IPv6 优先导致"客户端超时、服务端无日志"。getent hosts 会同时给出 AAAA,客户端按系统策略优先连 IPv6;如果服务端只监听 IPv4,就会在 TCP 层超时,而服务端连 SYN 都收不到。用 curl -4 / -6 分别试一次,能在一分钟内确认。

第四,把 TLS 握手慢当成"网络慢"。上面那 1.118 秒的 TLS 耗时里,包含了 TCP 之上的密钥交换、证书链传输与验证,可能来自证书链过长、OCSP 抓取超时、或者服务端 CPU 紧张。区分方法很简单:time_connect 小、time_appconnect 大,就是 TLS 段的问题;两者都大才是网络 RTT 的问题。

把四层检查写成一个脚本

#!/bin/bash
H=$1; P=${2:-443}
getent hosts "$H" >/dev/null || { echo "DNS fail"; exit 6; }
timeout 5 bash -c "cat < /dev/null > /dev/tcp/$H/$P" 2>/dev/null || { echo "TCP fail"; exit 7; }
openssl s_client -connect "$H:$P" -servername "$H" -brief </dev/null 2>&1 \
  | grep -q "CONNECTION ESTABLISHED" || { echo "TLS fail"; exit 35; }
curl -sS -o /dev/null -w "HTTP %{http_code}\n" --max-time 15 "https://$H/" || exit 1

三段退出码与 curl 的 6/7/35 对齐,好处是同一套编号在脚本和人工排查里通用,看到 35 就知道不用再查 DNS。脚本里 getent hosts 只判"有没有记录",不判记录是否正确——域名指向了错误地址时它照样返回成功,所以 DNS 层的最终确认还是要看一眼解析出来的 IP 是否符合预期。

容器与代理:两层额外变量

容器内的网络排查多两个变量。一是网络命名空间:容器有自己的 resolv.conf、自己的路由表、自己的 iptables,宿主上 curl 通不代表容器里通,反之亦然,所以命令要在出问题的那一侧执行。二是代理环境变量:HTTP_PROXY、HTTPS_PROXY、NO_PROXY 在 CI 镜像里经常被全局注入,一旦命中,curl 的流量会先到代理,前面四层的结论全部失效。curl -v 的 Connected to <proxy>:<port> 是识别它的最快方式。

这两层都不在 DNS→TCP→TLS→HTTP 的链条里,却能让链条上的结论全错,所以脚本跑完之后仍要人工看一眼 curl -v 的 Connected to 那一行。

为什么是这个顺序

四层的依赖关系是单向的:没有 DNS 结果就没有目标地址,没有 TCP 连接就没法开始 TLS 握手,没有 TLS 会话就发不出 HTTPS 请求。按依赖方向排查,每一步的失败都能被上一步的成功所排除;反过来从 HTTP 报错往回猜,会同时面对"名字错了""端口封了""证书过期"三种可能,最后还是要回到最前面重来。

另一个好处是耗时分布自然暴露瓶颈。四层的耗时量级差别很大:DNS 通常是毫秒级(有缓存时接近 0),TCP 是 RTT 量级,TLS 多一到两个 RTT,HTTP 取决于服务端处理时间。本次实测里 DNS 1.4ms、TCP 2.2ms、TLS 1118ms,一眼就能看出异常集中在 TLS 段,不需要再逐层加日志。

小结

四层排查的关键是"每层只回答一个问题",并且用机器可读的退出码或耗时字段固化结论。日常排障用 curl -w 一条命令拿到四层耗时,脚本里用退出码 6/7/35 分流,人工确认某台机器用 --resolve 绕开 DNS。

拿到分层结论之后,查的方向也就定了:DNS 层查解析记录与 resolv.conf,TCP 层查安全组、监听地址、防火墙,TLS 层查证书与协议版本,HTTP 层查应用日志。四层之外还有一层常被忽略——代理。容器或 CI 里如果设了 HTTP_PROXY,前面四层全部正常但流量走了别处,curl -v 的 Connected to 一行会显示代理地址,一眼能看出来。

数据来源与检索时间

来源URL对应数据
man7.org · ss(8)https://man7.org/linux/man-pages/man8/ss.8.html-t/-n/-l 的用法、TCP 状态名(syn-sent/time-wait 等)用于判断卡在握手哪一步
docs.openssl.org · openssl-s_clienthttps://docs.openssl.org/3.5/man1/openssl-s_client/-connect/-servername/-brief 的语义、wrong version number 出现的原因(对非 TLS 端口发起握手)
man7.org · ss(8)https://man7.org/linux/man-pages/man8/ss.8.htmlstate syn-sent 过滤可用于确认 SYN 是否发出、是否有回包

检索时间:2026-11-10(Asia/Shanghai)。

核验说明

  • 真跑:getent hosts / dig +short A 对 example.com 的解析结果、nc -z 与 /dev/tcp 的连通性判断、openssl s_client -brief 的协议版本、curl -w 的四段耗时(dns=0.001446s tcp=0.002181s tls=1.118325s total=1.756965s),均在 debian:12-slim 容器内实测。
  • 真跑:四类失败报错原文(curl: (6)、curl: (7)、curl: (35) wrong version number、HTTP 404)为本机实际输出。
  • 未验证:--resolve 固定到单台地址后对"时通时不通"的复现未构造;IPv6 优先导致超时的场景未构造(容器内无独立 IPv6 出口);代理层那一节属经验说明,未在本次实验中设置 HTTP_PROXY 验证。
  • 仅依据文档:ss 的 state syn-sent 过滤用法按 ss(8) 记录,本次未用它做过实际抓包对照。