TCP/IP:找出坏掉的那一层

「网络挂了」不是诊断结论。一次连接要走过域名解析、路由、目标 port、TCP、通常还有 TLS,最后才是应用层协议。按这个顺序一个个测,笼统的「故障」就会收敛成某一步的失败。

🎙️ 发布并录制于: ·

01分层把一次请求变成一串 packet

你的应用把字节写进一个 socket。TCP 把这条字节流切成 segment,负责顺序,也负责重传丢掉的数据。IP 把这些 segment 装进 packet,让每个 packet 朝着目标地址走。以太网或者 Wi-Fi 在当前这一跳上搬运帧。路由器只关心 IP,它看不懂你的 JSON 错误。

HTTP request bytes
  ↓ application
TLS records              # when HTTPS is used
  ↓
TCP segments             # ordered, reliable byte stream
  ↓
IP packets               # source and destination addresses
  ↓
Ethernet / Wi-Fi frames  # one local hop at a time
这个模型是排查工具如果 DNS 拿不到地址,TCP 还没开始。如果 TCP 连接被拒,HTTP 还没开始。如果 TLS 拒了证书,改 REST 路由是没用的。别再把「任何 API 请求都还没发出去」的失败叫成「接口问题」。

02DNS 管名字,IP 管路由

DNS 把一个主机名翻译成一个或多个 IP 地址。IPv4 长得像四个十进制数字;IPv6 是十六进制,空间大得多。解析完成之后,IP 路由再挑一条通往这个地址的路。同一个主机名可能因为地区、网络或时间返回不同的地址,所以要记下出问题的那个客户端实际拿到的地址。

# Resolve without making a TCP connection
nslookup api.example.com
# or
dig api.example.com A +short
dig api.example.com AAAA +short

# Inspect the route, not the application
tracert api.example.com       # Windows
traceroute api.example.com    # Linux/macOS

别把生产环境的 IP 直接敲进一个 HTTPS URL,然后管它叫干净的 DNS 测试。TLS 选证书和 HTTP 的虚拟主机都需要主机名。用 curl --resolve 钉住一个地址,同时保留真实的 host 名。

03Port 选中那个在监听的进程

IP 地址把流量送到一台机器或者一个网络接口。port 把它送到某个服务。一个监听中的 socket 绑在本地的某个地址和 port 上。一条已连接的 TCP socket 是由源地址、源 port、目标地址、目标 port 四元组标识的,所以几千个客户端可以同时跟同一个服务端 port 说话。

client  192.0.2.44:53182  →  203.0.113.10:443  server
        temporary port                   HTTPS listener

# See listeners
ss -lntp                       # Linux
netstat -ano | findstr LISTEN  # Windows
lsof -nP -iTCP -sTCP:LISTEN    # macOS/Linux
EADDRINUSE 按顺序修Error: listen EADDRINUSE: address already in use 0.0.0.0:3000第一步:sslsof 或者 netstat -ano 看一下三千这个 port。第二步:把 PID 对到具体进程上。第三步:优雅地停掉那个残留实例,或者换一个 port。第四步:重启,然后确认只有一个预期中的监听者。把所有叫 Node 的进程全杀掉,只会盖住进程管理上的 bug,还可能顺手停掉无关的应用。

04TCP handshake 证明两个方向都通

应用字节流动之前,客户端先发 SYN,服务端回 SYN-ACK,客户端再回 ACK。这一来一回协商了初始序列号,也确认了两个方向都有通路。之后 TCP 给你的是一条有序的字节流。它没有「消息」这个概念:两次写可能被一次读全拿到,一次写也可能要读好几次。

client                                  server
  | -------- SYN, seq=x --------------> |
  | <----- SYN-ACK, seq=y, ack=x+1 ---- |
  | -------- ACK, ack=y+1 ------------> |
  | ===== application byte stream ===== |
  | -------- FIN ---------------------> |  # orderly close

TCP 的可靠性不等于无限的耐心。重传发生在你的应用底下,但应用自己仍然需要连接超时、读超时和整体截止时间。一次成功的 handshake 只能证明:那个地址和 port 上有东西接了 TCP。它证明不了 TLS、鉴权,或者你请求的那条 HTTP 路由是好的。

05连接被拒和超时不是一回事

Refused 是一个很快的回答:目标主机,或者某条主动拒绝规则,明确说了没人接这个连接。Timeout 是沉默:packet 被丢了、被路到了没用的地方,或者回包回不来。把两者都当成「服务挂了」,等于浪费了网络已经交给你的信息。

Error: connect ECONNREFUSED 127.0.0.1:5432
Error: connect ETIMEDOUT 203.0.113.10:443

# Test only TCP reachability to the destination port
Test-NetConnection api.example.com -Port 443  # PowerShell
nc -vz api.example.com 443                    # Linux/macOS
curl -v --connect-timeout 5 https://api.example.com/
ECONNREFUSED 按顺序修第一步:核对报错里的 host 和 port,特别留意是不是误连了 127.0.0.1第二步:到服务端确认那个服务真的在跑。第三步:看一下监听者绑的地址和 port。第四步:先在服务端本机测,再从客户端测。如果本机通、远程被拒,就去查 bind 地址、容器端口发布,以及主动拒绝规则。
ETIMEDOUT 按顺序修第一步:解析主机名,把每一条 IPv4 和 IPv6 结果都记下来。第二步:做一次针对具体 port 的测试,不要只 ping。第三步:检查客户端出网策略、云上安全规则、主机 firewall、路由、VPN 和 NAT 的回包路径。第四步:在服务端抓包:完全没有 SYN,说明路上被丢了;有 SYN 但没有回复,说明服务端的路径或 firewall 配错了。随手重启应用不是超时的诊断。

06localhost 是私有的;0.0.0.0 是一条绑定指令

127.0.0.1::1 是回环地址。流量永远不会离开那个网络命名空间。绑在回环上的服务只接受本地客户端。绑到 0.0.0.0 的意思是「在所有 IPv4 接口上监听」,它不是客户端该去连的目标地址。在容器里,localhost 指的是这个容器,不是宿主机,也不是另一个容器。

# Reachable only inside the same host or container
server.listen(3000, "127.0.0.1")

# Reachable on the container's interfaces; publish separately
server.listen(3000, "0.0.0.0")
docker run -p 8080:3000 my-app

# Verify the actual bind
ss -lntp | grep :3000
LISTEN 0 511 127.0.0.1:3000 ...
容器里面通,外面被拒第一步:进到容器里跑 curl http://127.0.0.1:3000第二步:ss -lnt;如果显示的是 127.0.0.1:3000,就把应用的 bind 改成 0.0.0.0第三步:确认容器把 port 发布出来了,比如宿主机八千零八十映射到容器三千。第四步:测宿主机上的那个 port。第五步:到这一步再去看宿主机或云上的 firewall。把 port 暴露出去,修不了一个绑在容器回环上的应用。

07NAT 会重写私网里的对话

私有 IPv4 段可以在不同网络里重复使用,公网上不会路由它们。出向连接经过 NAT 网关时,私有的源地址和 port 会被改写成一个公网地址,回包再按映射还原回来。没有映射的入向流量进不来,除非你加了端口转发或者一个负载均衡器。

10.0.0.24:53182  →  NAT  →  198.51.100.7:62014  → internet
private host                  public mapping

Private IPv4:
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16

# Public-looking address from outside
curl https://ifconfig.example  # use a trusted endpoint you control

NAT 不是安全策略。有状态的映射恰好挡住了大多数未经请求的入向流量,但授权该由 firewall 和应用来做。另外记住运营商级 NAT:可能有两层路由器在做转换,家里想做端口转发就不可能,除非运营商支持、改用 IPv6、打隧道,或者走中继。

08Firewall 按方向、四元组和连接状态判断

一条 firewall 规则通常会看协议、源、目标、port、方向、接口和连接状态。云上的网络在主机 firewall 之前还有安全组和网络 ACL。Kubernetes 又加了 Service 和 NetworkPolicy。如果另一层照样丢掉同一个 packet,那某个面板上的一条绿规则说明不了什么。

# Minimum question, stated precisely
Allow TCP from 198.51.100.0/24
         to 10.0.2.15 destination port 443
         with return traffic for established connections

# Linux host checks vary by system
sudo nft list ruleset
sudo ufw status verbose

# Windows
Get-NetFirewallProfile
Get-NetFirewallRule -Enabled True
不要「先把所有 firewall 关了试试」那会毁掉证据,还可能把机器直接暴露出去。加一条窄的、带日志的规则,精确写清源、目标、协议和 port;重测;然后要么撤掉它,要么正式化。如果规则的 packet 计数一直是零,说明流量根本没走到这条规则。接着往外查路由表、负载均衡器和云上的管控。

09先 DNS,再 TCP,再 TLS,最后 HTTP

一次 HTTPS 请求是一叠依赖。DNS 返回一个 IP。TCP 连上一个 port。TLS 验证主机名并建立加密。HTTP 发出方法、路径、请求头和 body。就按这个顺序测。我的态度很明确:ping 是很弱的可用性测试。很多健康的服务器会屏蔽 ICMP,而一台能回 ping 的机器,应用可能根本没起来。

# 1. DNS
nslookup api.example.com

# 2. TCP port
Test-NetConnection api.example.com -Port 443

# 3 and 4. TLS plus HTTP, with verbose evidence
curl -v --connect-timeout 5 https://api.example.com/health

# Bypass DNS while preserving hostname for TLS and HTTP
curl -v --resolve api.example.com:443:203.0.113.10 \
  https://api.example.com/health

# Listener ownership
ss -lntp | grep :443
netstat -ano | findstr :443
lsof -nP -iTCP:443 -sTCP:LISTEN
看清 curl 停在哪一步Could not resolve host 是 DNS。Failed to connect 是 TCP。certificate verify failed 是 TLS 的身份或信任问题。一个 HTTP 404 说明 DNS、TCP,通常还有 TLS 都通了;这时候去看 Host 请求头和路径。证书出错,永远不要拿 -k 当生产环境的修法。

10TCP/IP 排查速查表

在真正出问题的那台机器或者那个容器里跑测试。从你笔记本上请求成功,证明的是你笔记本的路径,不是生产 worker 的路径。

1. Record exact HOST:PORT and failing client location
2. Resolve A and AAAA; note the returned addresses
3. Test TCP to that exact port with a short deadline
4. On server: verify process + bound address + listener
5. Check container/service port publication
6. Check route, NAT, cloud rules, and host firewall
7. Test TLS with the real hostname
8. Send the smallest HTTP request; read status and headers

ECONNREFUSED → host answered; no listener or active reject
ETIMEDOUT    → dropped path, wrong route, or missing return path
EADDRINUSE   → another socket already owns address and port
404          → network stack worked; inspect host/path/routing
TLS error    → TCP worked; inspect name, chain, date, trust

localhost    → this network namespace only
0.0.0.0      → listen on all IPv4 interfaces; not a client address
ping works   → only ICMP replied
ping fails   → server may still be healthy

Best evidence: timestamp, client, resolved IP, HOST:PORT,
listener output, curl -v trace, and firewall packet counters.

值得留住的规则很简单:改任何东西之前,先说出是哪一层失败了。解析名字,摸到地址,连上 port,验证 TLS,然后再说 HTTP。当每次测试只问一个问题、下一步动作由那个确切答案决定时,「网络」这件事就变得可控了。

Tell me what missed

A correction is more useful than a compliment. This goes straight to the person who writes SwiftGrasp.

Was this page useful?
0/1000

Please do not include passwords, private keys, or personal information.