Old-School Networking / 2026 ・ No.12
古法网络入门
第12讲(最终讲) 连不上时的排查 ── 从下层开始,一级一级
发布
“按了申请按钮,什么反应也没有。”最终讲,就从这句话开始调查。
把第 9、10 讲的练习网络,按网线、路、对方的机器、名字、端口、锁、内容,一次故意弄坏一个地方。每次出现的错误信息,以及用什么工具找出卡在哪一层的步骤,都从下层开始依次确认。
“什么反应也没有”
新井前辈,财务来电话了,说“按了申请按钮什么反应也没有”。是服务器挂了吗?
古井可能是挂了,也可能是网线掉了,也可能是名字查不到。别一下子跳到答案。
新井那从哪儿开始?
古井从下面开始。用手指把花了 11 讲做出来的地图划一遍。
全 12 讲路线图(当前位置:第 12 讲)
- 排查的梯子 ── 每层一个问题、一个工具,从下往上
- 弄坏 7 种 ── 网线、路、对方的机器、名字、端口、锁、nginx 后面。每次的信息和时间(实测)
- 被拒绝,还是被丢弃 ── 0.041 秒的 RST 和 10 秒的超时(实测与 Wireshark)
- 慢的时候 ── 用 curl 把时间分成名字、TCP、TLS、服务器(实测)
练习网络和第 9、10 讲相同。电脑和运营商的解析器在线的这一边,DNS 服务器和申请服务器在单程 20 毫秒的捣乱线对面。这次在申请服务器前面还放了 nginx(80 号)。每次只弄坏一个地方,记录后再恢复原样。
排查的梯子
排查的第一步和第 11 讲一样,先用一行写下从哪里、发往哪里的几号。这次就是“从电脑,发往 expenses.example 的 3443 号,用 HTTPS”。然后每层问一个问题。
每层一个问题、一个工具
梯子要从下往上爬。上面一级架在下面一级上:口没连上就查不到名字,查不到名字 TCP 就开始不了,没有 TCP 就谈不上 TLS 和 HTTP。反过来,某一级通了,就证明它下面全都在工作,不用再怀疑了。
别被错误信息骗了
首先,拔掉网线(练习时是把线对面的口关掉)。
# 拔掉网线(把线对面的口关掉)
pc$ curl https://expenses.example:3443/api/expenses
curl: (6) Could not resolve host: expenses.example (6.025 s)
# 先看自己的口
pc$ ip -br link show eth0
eth0 DOWN <NO-CARRIER,BROADCAST,MULTICAST,UP>
pc$ ping -c2 198.51.100.53
2 packets transmitted, 0 received, 100% packet loss
curl 的信息是“Could not resolve host”,查不到名字,而且是等了 6.02 秒之后。可是 DNS 什么也没坏。curl 最先要做的是查名字,在那里卡住了,所以才这么写。错误信息只说出最上面发生的事。从梯子下面看,ip link 的 NO-CARRIER(对面没有人)一目了然。
说“DNS 坏了”之前,先看自己的口,再 ping 一下解析器。Wi-Fi 没连好、网线掉了、VPN 断了,常常会以查名字失败的样子出现。
地址与路
接下来是第 3 层。先删掉去往 203.0.113.0/24 的路(路由)。
# 删除去往 203.0.113.0/24 的路(路由)
pc$ curl -v https://expenses.example:3443/api/expenses
* Trying 203.0.113.20:3443...
* Immediate connect fail for 203.0.113.20: Network is unreachable
curl: (7) Failed to connect … after 0 ms: Couldn't connect to server
pc$ ip route get 203.0.113.20
RTNETLINK answers: Network is unreachable
没有路,数据包连电脑都出不去。0 毫秒就是“Network is unreachable”。用 ip route get 可以直接问去往那个地址的路有没有。
这次是有路、但对方的机器不在的情况。把申请服务器的地址 203.0.113.20 摘掉。
# 对方的机器不在(摘掉 203.0.113.20)
pc$ curl https://expenses.example:3443/api/expenses
curl: (7) Failed to connect … after 3053 ms: Couldn't connect to server
# 邻居的地址(MAC)查到了吗
pc$ ip neigh show 203.0.113.20
203.0.113.20 dev eth0 FAILED
pc$ ping -c3 203.0.113.20
3 packets transmitted, 0 received, 100% packet loss
curl 等了 3.05 秒,“连不上”。封面照片就是这时电脑的口。用第 3 讲的 ARP 问了 3 次“203.0.113.20 是谁?”,没有回音就结束了。ip neigh 的 FAILED 是“查不到邻居的地址(MAC)”的记录,要怀疑对方的机器停了,或者地址设置错了。
名字
名字这一级,用两种方式弄坏:停掉解析器,以及把名字打错。
# ① 解析器停了
pc$ curl https://expenses.example:3443/api/expenses
curl: (6) Could not resolve host: expenses.example
pc$ dig expenses.example
;; communications error to 198.51.100.53#53: connection refused
# ② 名字打错(exmaple)
pc$ curl https://expenses.exmaple:3443/api/expenses
curl: (6) Could not resolve host: expenses.exmaple
pc$ dig expenses.exmaple
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN
# 正确的名字
pc$ dig +short expenses.example
203.0.113.20
curl 的信息两者都是“Could not resolve host”,分不出来。用 dig 就清楚了。解析器停了是“communications error”(根本拿不到回答),打错是 status: NXDOMAIN(好好地拿到了回答,回答是“没有这个名字”)。前者是第 3 层为止或解析器的问题,后者是名字本身的问题。
正如第 9 讲所说,dig 不看 hosts。只有应用表现异常时,也用 getent hosts(Mac 用 dscacheutil)查一下,怀疑 hosts 里的旧条目。
端口:被拒绝,还是被丢弃
第 4 层最重要的,是区分被拒绝还是被丢弃。比较停掉服务器程序,和让对方防火墙默默丢弃两种情况。
# ① 服务器程序停了(没有人在那个端口等)
pc$ curl https://expenses.example:3443/api/expenses
curl: (7) Failed to connect … after 41 ms: Couldn't connect to server
pc$ nc -vz expenses.example 3443
… port 3443 (tcp) failed: Connection refused
srv$ ss -ltn 'sport = :3443'
(无)
# ② 对方的防火墙默默丢弃(DROP)
pc$ curl --connect-timeout 10 https://expenses.example:3443/api/expenses
curl: (28) Failed to connect … after 10002 ms: Timeout was reached
pc$ nc -vz -w 3 expenses.example 3443
… port 3443 (tcp) timed out: Operation now in progress
立刻回来的 RST,和没有回音的 SYN
程序停了,对方的操作系统会用 RST 立刻回答“这个端口没人在等”。0.041 秒正好是捣乱线的一个来回。已经到达对方的机器,所以要看对方的程序在不在运行(ss -ltn),端口对不对。
防火墙丢弃时,什么也不会回来。电脑像第 6 讲看到的那样重发 SYN,curl 在 10 秒后超时。这是在途中某处消失了,要怀疑防火墙、安全组、途中的路。
锁
TCP 连上了,也可能卡在 TLS 这一级。不告诉 curl 练习用的 CA,就用 HTTPS 发送。
# 不认识练习用的 CA 就去访问 HTTPS
pc$ curl https://expenses.example:3443/api/expenses
curl: (60) SSL certificate problem: unable to get local issuer certificate
# 用 openssl 直接看对方出示的证书
pc$ openssl s_client -connect expenses.example:3443 -servername expenses.example
subject=CN = expenses.example
issuer=CN = Kiji Practice Root CA
Verify return code: 21 (unable to verify the first certificate)
# 告诉它 CA 之后
pc$ openssl s_client … -CAfile ca.pem
Verify return code: 0 (ok)
openssl s_client 是直接查看对方出示的证书的工具。能看到“谁的证书(subject)”“谁签发的(issuer)”和验证结果(Verify return code)。告诉它 CA 就是 0(ok)。第 8 讲的 4 种情形(不认识签发者、名字不对、过期、自签名)在这里分辨。解决办法是告诉它正确的 CA,或者修好证书。用 -k 关掉验证不算修好。
内容:5xx 是谁回的
最后一级是 HTTP。在申请服务器前面放 nginx,用两种方式弄坏它后面:忘了启动新版本(/v2/)的应用,以及应用太慢。
# 申请服务器前面有 nginx(80 号)。/v2/ 后面(4000 号)忘了启动
pc$ curl http://expenses.example/api/expenses
→ 200 (0.089 s)
pc$ curl http://expenses.example/v2/api/expenses
→ 502 Bad Gateway (0.083 s)
pc$ curl 'http://expenses.example/api/report?ms=3000&n=1'
→ 504 Gateway Time-out (2.085 s)
# nginx 的记录(error.log,节选)
connect() failed (111: Connection refused) while connecting to upstream,
upstream: "http://127.0.0.1:4000/api/expenses"
upstream timed out (110: Connection timed out) while reading response header
from upstream, upstream: "http://127.0.0.1:3000/api/report?ms=3000&n=1"
返回 5xx 的是前面的 nginx,出问题的是它后面
有回复来,就说明下面五级全都通了。要看的是谁回的。502 和 504 都是电脑眼前的 nginx 回的(照片里的 Server: nginx)。502 是“想连后面却被拒绝”,504 是“后面没在 2 秒内回答”。原因写在 nginx 的 error.log 里,附带后面的地址。4xx 是请求方式(第 10 讲),5xx 是服务器一侧,其中 502、504 是“服务器的再后面”。
慢的时候
“连不上”的亲戚是“慢”。慢的时候,把时间按级分开。curl 的 -w 会告诉你从开始算起,查名字、TCP、TLS、收到第一个字节的时刻。
# 清掉解析器的记忆后,获取 2 次需 300 毫秒的报告(用 curl -w 分解时间)
pc$ curl -w 'dns %{time_namelookup} tcp %{time_connect} tls %{time_appconnect}
first-byte %{time_starttransfer} total %{time_total}\n' …
第 1 次 dns 0.166 tcp 0.206 tls 0.252 first-byte 0.595 total 0.595
第 2 次 dns 0.002 tcp 0.043 tls 0.090 first-byte 0.434 total 0.434
0.595 秒的明细(实测)
取差值,第 1 次是名字 0.166 秒、TCP 握手 0.041 秒、TLS 握手 0.045 秒、服务器处理与回复 0.344 秒。第 2 次名字缩短到 0.002 秒(第 9 讲的缓存)。两次最长的都是服务器一侧约 0.34 秒(300 毫秒处理+一个来回),不是网络的问题。说“网络慢”之前,先看这个分解。
速查表与在自己的电脑上
这个连载里实际遇到的错误信息,以及首先要怀疑的那一级。
| 信息(例) | 首先怀疑 | 第一步 |
|---|---|---|
| Could not resolve host | 名字,但也包括它下面 | ip link 和 dig |
| Network is unreachable | 路(没有路由) | ip route get 地址 |
| 等了几秒连不上(ARP 没回应) | 对方的机器、地址设置 | ip neigh |
| Connection refused | 端口(到了,但没人在等) | 在对方 ss -ltn |
| Timeout was reached | 途中被丢弃 | 防火墙、nc -vz |
| SSL certificate problem | 锁(第 8 讲的 4 种情形) | openssl s_client |
| 502 / 504 | 前面服务器的再后面 | 前面的 error.log |
| CORS 错误 | 浏览器的守门人(第 10、11 讲) | API 返回的头部 |
| ECONNREFUSED 127.0.0.1 | 弄错了 localhost(第 11 讲) | 从哪里发的 |
梯子上的工具,在不同操作系统里名字略有不同。
| 级 | Mac | Windows | Linux |
|---|---|---|---|
| 线与口 | ifconfig | ipconfig /all | ip -br link |
| 路 | netstat -rn | route print | ip route get 地址 |
| 邻居 | arp -a | arp -a | ip neigh |
| 名字 | dig 名字 | Resolve-DnsName 名字 | dig 名字 |
| 端口 | nc -vz 名字 端口 | Test-NetConnection 名字 -Port 端口 | nc -vz 名字 端口 |
| 锁与内容 | curl -v https://名字/ | ||
结 — 全 12 讲一页总结
从下到上追踪申请按钮一次通信的 12 讲
- 先写一行:从哪里、发往哪里的几号。
- 梯子从下往上。通过的那一级以下不再怀疑。
- 错误信息只说出最上面发生的事。网线掉了也会是“Could not resolve host”。
- 被拒绝(立刻)说明到达了对方,超时(要等)说明在途中消失了。
- 名字用 dig 查,分清停了的解析器和 NXDOMAIN。
- 锁用 openssl s_client 查,不关验证地修好。
- 5xx 要看是谁回的。慢的时候按级分解时间。
新井财务那件事,从 ip link 开始依次看,原来是无线切换时断了线。服务器好好的。
古井干得漂亮。有了 12 讲的地图,卡在哪里都不会迷路。
新井接下来,我想了解接收这个申请的服务器内部。
古井好啊。网络之旅就到这里。接下来,到服务器里面去吧。
参考
- ip-link(8) / ip-route(8) / ip-neighbour(8)
- dig(1)
- OpenSSL 3.0 — openssl-s_client
- Everything curl — Write out(-w)
- nginx — ngx_http_proxy_module(proxy_read_timeout)
- RFC 9110 — HTTP Semantics(502, 504)
- RFC 9293 — Transmission Control Protocol (TCP)
- RFC 826 — An Ethernet Address Resolution Protocol
- Microsoft Learn — Test-NetConnection
记录来自笔者在云端 Linux(Ubuntu 24.04,内核 6.18)上用网络命名空间搭建的练习网络,于 2026 年 10 月 25 日抓取。构成与第 9、10 讲相同(解析器 Unbound 1.19.2,DNS 服务器 NSD 4.8.0,申请服务器 Node.js 22.23),在申请服务器前面放了 nginx 1.24.0,电脑一侧是 curl 8.5.0 和 OpenSSL 3.0。名字和地址都是为说明而保留的或为练习而制作的。电脑和服务器之间是第 6 讲的捣乱线(单程 20 毫秒)。表中的数字和输出都是从这些记录和当时命令的输出读出的实际数值(较长的输出省略了一部分)。登场人物均为虚构。感谢您读完全部 12 讲。
古法网络入门 第12讲 — 连不上时的排查 / 2026.10