Appearance
14|网络诊断与排错
服务起了但外部访问不了。ping 一下——通,说明本机到目标网络层是通的。curl 一下——报 connection refused,说明目标端口没开;报 connection timed out,说明被防火墙挡了或网络不通。同样是"访问不了",三种现象指向三种不同根因。
网络故障排查的核心方法是分层定位、由近及远:从本机最近的一层开始,逐层向外确认。底层断了上层一定不通,跳过底层直接查上层一定走偏。本篇讲分层排查的标准顺序,每一层用什么工具、看什么信号,再展开常见网络故障的判定和处理。
一、网络排查的标准分层
| 层次 | 确认什么 | 主要工具 |
|---|---|---|
| 1. 物理层 | 网卡 link 是否 up,速率是否正常 | ip link、ethtool eth0 |
| 2. 网络层(本机) | 网卡有没有 IP,IP 是否正确,路由表是否合理 | ip addr、ip route、ip route get <目标> |
| 3. DNS | 域名能否解析,解析结果是否正确 | getent hosts、dig |
| 4. 网络层(连通性) | 能否到达目标 IP,中间路径在哪一跳断 | ping、mtr、traceroute |
| 5. 传输层 | 目标端口是否开放,TCP 是否能建立 | ss、nc、curl -v |
| 6. 应用层 | 应用是否正常响应,响应内容是否符合预期 | curl、应用日志 |
| 7. 数据包级 | 包到底走到哪一步、被谁丢了 | tcpdump、tshark |
遵循这个顺序的好处:每一层都用最便宜的工具(查命令几秒钟),问题往往在前几层就暴露,根本不需要走到抓包这一步。直接上抓包是最低效的排查方式。
第 0 步永远是这四条:摸基本面
不管什么网络问题,先把这四条跑一遍,大量问题这一步就暴露了:
bash
ip addr # 网卡是否 UP、IP 是否符合预期
ip route # 默认路由和静态路由
cat /etc/resolv.conf # DNS 服务器
ss -lntp # 本机监听的所有端口跑完这四条后,十有八九能定位到问题方向——网卡 DOWN、缺默认路由、DNS 配错、服务没起来,都是肉眼可见的。
二、ping 与 ICMP 局限
bash
ping -c 4 8.8.8.8 # ping IP
ping -c 4 example.com # ping 域名
ping -I eth1 -c 4 8.8.8.8 # 指定从 eth1 发出,验证特定网卡的连通性判定逻辑:
- IP 能通、域名不通 → DNS 问题
- IP 也不通 → 继续查路由、防火墙、对端可达性
ping 不通不等于业务不通
很多服务器和网络设备主动禁用 ICMP 响应(为了防探测、防 DDoS),所以 ping 不通不能用来证明业务端口不通。判断业务可达,直接测业务端口:
bash
nc -zv host 80 # 测 TCP 80 端口
curl -v http://host:80 # 更彻底的 HTTP 测试反过来,ping 通也不等于业务通——能 ping 通只说明 ICMP 路径可达,业务端口可能被防火墙单独拦掉。两个工具配合用,结论才靠谱。
MTU 探测
ping 一个少被用的能力——带 -M do -s <size> 探测路径 MTU。这在排查 MTU 黑洞时是关键工具:
bash
# 发 1472 字节的 ICMP(加上 28 字节 IP/ICMP 头 = 1500,标准以太网 MTU),不分片
ping -M do -s 1472 -c 3 8.8.8.8
# 输出 OK 表示路径 MTU >= 1500
# 发 1473 字节,不分片
ping -M do -s 1473 -c 3 8.8.8.8
# 输出 "Frag needed and DF set" 表示路径 MTU < 1501
# 二分法找到准确 MTU
ping -M do -s 1400 -c 3 8.8.8.8 # OK?
ping -M do -s 1450 -c 3 8.8.8.8 # OK?MTU 相关故障在第八节"MTU 黑洞"展开。
三、traceroute 与 mtr:看路径
traceroute 显示数据包经过的路径,逐跳定位:
bash
traceroute -n 8.8.8.8 # -n 不解析主机名,加快显示
traceroute -T -p 443 host # 用 TCP SYN 探测(很多设备 ICMP 被禁但 TCP 能过)mtr 是 traceroute + ping 的合体版本,持续统计每一跳的丢包率和延迟,排查链路质量问题时比 traceroute 强得多:
bash
mtr 8.8.8.8 # 默认交互界面
mtr -rwbc 30 8.8.8.8 # -r 报告模式, -w 宽输出, -b 同时显示 IP, -c 30 发 30 个包-rwbc 30 的输出格式适合写报告、贴工单:
Host Loss% Snt Last Avg Best Wrst StDev
1. 192.168.1.1 0.0% 30 0.5 0.5 0.3 0.8 0.1
2. 10.0.0.1 0.0% 30 1.2 1.3 1.1 2.0 0.2
3. ??? 100.0% 30 0.0 0.0 0.0 0.0 0.0
4. 203.0.113.5 0.0% 30 8.0 8.5 7.8 12.0 0.9
5. 8.8.8.8 0.0% 30 9.0 9.2 8.8 11.0 0.5中间跳 100% 丢包不一定是真故障
最大的误判点:看到中间某一跳丢包率高甚至 100%,不能直接断定那一跳的设备有问题。原因:
- 很多路由器限制 ICMP 回复速率(rate limiting)——一秒钟只回应几个 ICMP,超过的丢弃
- 部分设备完全不回复中间跳的 TTL 过期消息(传统上叫 "ICMP time exceeded"),但仍然正确转发数据包
- 上面输出里第 3 跳显示
???100% 丢包,但第 4、5 跳正常 → 第 3 跳只是不回应,转发功能正常
真正要看的是最终目的地的丢包率和延迟。终点正常 = 链路正常,中间跳的丢包是工具显示限制造成的假象。
终点真有丢包时,从尾跳往前看,第一个开始出现高丢包率的跳点 = 实际故障点——而不是最后一跳。
四、curl:HTTP 层验证
HTTP 层排查首选 curl,功能比 wget / telnet / 浏览器都丰富:
bash
curl -I http://example.com # 只看响应头(HEAD 请求)
curl -v http://example.com # 显示完整请求-响应过程
curl -fsS http://127.0.0.1:8080/healthz # 健康检查标准写法
curl -w "@curl-format.txt" -o /dev/null -s http://example.com # 看详细计时高频参数速查
| 参数 | 作用 | 适用场景 |
|---|---|---|
-I | 只发 HEAD,只看响应头 | 快速看状态码、Content-Type |
-v | 显示连接全过程(DNS、TLS、请求头) | 排查 SSL 握手、重定向问题 |
-f | 4xx/5xx 时退出码为 22(失败) | 健康检查、自动化脚本 |
-sS | 静默但保留错误输出 | 自动化脚本中抑制进度条但保留 stderr |
-L | 自动跟随重定向 | 处理 301/302 跳转 |
-k | 忽略 TLS 证书校验 | 内网测试,生产脚本禁用 |
--resolve host:port:ip | 强制把域名解析到指定 IP | 测试单台后端是否正常,绕过负载均衡 |
--connect-timeout N | 连接阶段超时(秒) | 防止脚本卡死 |
-m N | 整体超时(秒) | 防止单个请求卡死 |
--resolve 在多副本排查中的价值
线上服务通常通过域名指向负载均衡器,后面挂多个真实后端。某次故障"50% 的请求失败"——很可能是某一个后端节点出问题,但通过域名访问每次随机命中不同后端,无法稳定复现。
--resolve 让你不修改 DNS 就能精准测试某一台后端:
bash
# api.example.com 实际是 LB,后面有 3 台后端
curl --resolve api.example.com:443:10.0.0.1 https://api.example.com/healthz
curl --resolve api.example.com:443:10.0.0.2 https://api.example.com/healthz
curl --resolve api.example.com:443:10.0.0.3 https://api.example.com/healthz
# 找到失败的那一台curl 的详细计时:定位"慢"在哪一阶段
curl -w 配合一个格式文件,可以看请求每个阶段的耗时:
bash
cat > /tmp/curl-format.txt <<'EOF'
namelookup: %{time_namelookup}s
connect: %{time_connect}s
appconnect: %{time_appconnect}s (TLS 握手)
pretransfer: %{time_pretransfer}s
redirect: %{time_redirect}s
starttransfer: %{time_starttransfer}s (首字节返回)
total: %{time_total}s
EOF
curl -w "@/tmp/curl-format.txt" -o /dev/null -s https://example.com输出示例:
namelookup: 0.0234s
connect: 0.0892s
appconnect: 0.3421s (TLS 握手)
pretransfer: 0.3422s
starttransfer: 0.4567s (首字节返回)
total: 0.4892s排查"接口慢"时,这几个时间段的拆分能立刻定位:
namelookup高 = DNS 解析慢connect - namelookup高 = TCP 握手慢(网络层)appconnect - connect高 = TLS 握手慢(证书链/CPU)starttransfer - appconnect高 = 服务端处理慢(应用层)total - starttransfer高 = 响应体下载慢(带宽/响应体过大)
五、DNS 诊断
DNS 问题占网络问题的相当比例,且常常被误判为"网络不通"。
bash
getent hosts example.com # 走系统 NSS,模拟应用真实的解析路径
dig example.com # 直接查 DNS,绕过 hosts 和缓存
dig @8.8.8.8 example.com # 指定 DNS 服务器,对比不同 DNS 的返回
dig +trace example.com # 从根域名服务器开始逐级追踪
dig -x 8.8.8.8 # 反向解析,IP → 域名getent 与 dig 必须对比看
应用程序解析域名走的是系统 NSS(nsswitch.conf 配置的顺序,通常是 files dns),dig 直接查 DNS 服务器,绕过本地配置。两者结果不一致是 DNS 故障最经典的特征:
dig example.com→ 新 IPgetent hosts example.com→ 旧 IP
这种情况:
bash
# 看是否被 /etc/hosts 截胡
grep example.com /etc/hosts
# 看 NSS 配置顺序
grep '^hosts:' /etc/nsswitch.conf
# hosts: files dns ← files 在 dns 前,/etc/hosts 优先/etc/hosts 里写死的旧记录、负载均衡切换后忘了清的临时记录,都会导致应用一直连到错误的目标。
dig +trace:层级解析失败定位
dig +trace 模拟递归解析过程,从根域名服务器(.)开始,依次查询 .com、example.com、www.example.com,显示每一级的返回:
bash
dig +trace example.com哪一级返回 SERVFAIL 或者返回了非预期的 NS,就说明那一级的权威服务器有问题。这是排查"自家域名解析失败"的核心手段。
DNS 解析慢的判定
应用偶尔出现"刚启动后第一个请求超时几秒,后面正常"——大概率是 DNS 解析慢:
bash
time dig @<本机 DNS> example.com
# 如果 real 时间超过 100ms,DNS 性能就有问题可能原因和处理:
- DNS 服务器响应慢 → 换 DNS 服务器,或在本机部署 dnsmasq / unbound 做本地缓存
- IPv6 解析顺序问题 →
/etc/gai.conf调整 IPv4/IPv6 优先级 /etc/resolv.conf的options参数不合理 → 调短 timeout、调小 attempts(详见第 13 讲第 5 节)
六、ss:查看本机连接
ss 是查看本机网络连接的主力工具,取代了老的 netstat(net-tools 包提供,已不推荐)。
常用命令
bash
ss -lntp # 查看 TCP 监听端口及进程(-l 监听 -n 不解析 -t TCP -p 进程)
ss -lnup # UDP 监听
ss -antp # 所有 TCP 连接(包括非监听状态)
ss -antp | grep ':3306' # 谁连了 MySQL
ss -tn state established 'dst :443' # 所有出站 HTTPS 连接
ss -s # 连接状态统计概览ss -s 是排查连接数问题的快速概览:
$ ss -s
Total: 234
TCP: 189 (estab 156, closed 22, orphaned 0, timewait 22)
Transport Total IP IPv6
RAW 0 0 0
UDP 8 5 3
TCP 167 133 34
INET 175 138 37
FRAG 0 0 0TCP 连接状态速读
State 含义 问题信号
LISTEN 服务在监听等连接 正常
ESTAB 连接已建立,正常传数据 正常
SYN-SENT 客户端发了 SYN,等服务端回应 大量 = 目标端口不响应或被防火墙
SYN-RECV 服务端收到 SYN,发了 SYN-ACK,等客户端 ACK 大量 = 可能是 SYN flood
FIN-WAIT-1 主动关闭方发了 FIN,等对方 ACK 大量 = 对端处理慢或半开
FIN-WAIT-2 收到了对方的 ACK,等对方的 FIN 大量 = 对端不发 FIN(应用 bug)
CLOSE-WAIT 被动关闭方,等本地应用 close() 大量 = 本地应用漏 close
TIME-WAIT 主动关闭方,等待 2*MSL 高并发短连接下大量是正常的
LAST-ACK 被动关闭方,发了 FIN,等对方 ACK 短暂出现按状态过滤:
bash
ss -ant state established | wc -l # ESTAB 数量
ss -ant state time-wait | wc -l # TIME-WAIT 数量
ss -ant state close-wait # 看哪些连接卡在 CLOSE-WAIT(应用漏 close 的典型信号)七、Connection refused vs Connection timed out
这是 TCP 连接失败时最关键的两种错误,反映完全不同的问题,处理方向完全不同。
| 错误 | 含义 | 典型耗时 | 排查方向 |
|---|---|---|---|
| Connection refused | 包到达对方,但端口拒绝(没监听或防火墙 reject) | 立即返回(< 1 秒) | 服务端:ss -lntp 看是否监听、防火墙是否 reject |
| Connection timed out | 包根本到不了对方,或对方收到但回包到不了 | 几十秒(SYN 重试) | 网络层:路由、防火墙 drop、对方主机离线 |
判定根据:错误返回的速度
refused是立即的——SYN 发出去,对方回了 RST(reset),本机立刻把 RST 翻译成Connection refusedtimed out是慢的——SYN 发出去,没有任何回应,本机 TCP 协议栈按指数退避重试 3-5 次,总耗时几十秒,最后超时
如果错误是立即返回的,直接去服务端查——这意味着包能到对方机器:
bash
# 在服务端
ss -lntp | grep ':<端口>' # 服务是否监听?监听在 0.0.0.0 还是 127.0.0.1?
iptables -L INPUT -n | grep <端口> # 防火墙是否 reject?如果错误是慢的(几十秒后超时),先不要去对方机器——大概率包根本没到对方。先在本机查链路:
bash
ping <对方IP> # ICMP 是否能通?
mtr -rw <对方IP> # 路径在哪一跳断?
ip route get <对方IP> # 流量走哪个网卡?
iptables -L OUTPUT -n # 出方向防火墙是否拦?八、企业里常见的网络故障场景
下面是网络层与传输层频繁遇到的具体故障,每条给现象、判定特征、处理路径。
conntrack 表满
现象:服务器突然大量新连接建立失败,但已有连接正常。dmesg 出现:
nf_conntrack: table full, dropping packet根因:Linux 启用 nftables/iptables 的 conntrack 模块时,每个连接占用一个 conntrack 条目。条目数有上限(net.netfilter.nf_conntrack_max),满了后新连接被丢弃。常见于:
- 高并发短连接业务(API 网关、Nginx 反代)
- 客户端不规范导致大量 TIME_WAIT
- 被慢速 SYN 扫描
判定与处理:
bash
# 查看当前 conntrack 使用率
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
# 如果 count 接近 max,基本可以确认
# 临时调大(立即生效)
sysctl -w net.netfilter.nf_conntrack_max=1048576
# 持久化
echo "net.netfilter.nf_conntrack_max = 1048576" >> /etc/sysctl.d/99-conntrack.conf
sysctl -p /etc/sysctl.d/99-conntrack.conf
# 调小 TIME_WAIT 在 conntrack 中的保留时间
echo "net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30" >> /etc/sysctl.d/99-conntrack.conf预防:监控指标采集 node_nf_conntrack_entries / node_nf_conntrack_entries_limit 使用率,超过 80% 告警。云原生场景(K8s 节点)默认 conntrack 上限通常不足,大流量集群必须调大。
TIME_WAIT 大量堆积
现象:ss -ant state time-wait | wc -l 显示几万甚至几十万 TIME_WAIT 连接。同时业务报"无法分配新端口"(Cannot assign requested address)。
根因:TIME_WAIT 是主动关闭连接方的正常状态,持续 60 秒(2 倍 MSL)。高并发短连接场景下(每秒几千次新连接),TIME_WAIT 会堆积。如果是作为客户端主动连接外部服务,会占用本地临时端口范围(默认 32768-60999,约 28K 个),端口耗尽后无法发起新连接。
判定:
bash
# TIME_WAIT 数量
ss -ant state time-wait | wc -l
# 临时端口范围
cat /proc/sys/net/ipv4/ip_local_port_range
# 默认 32768 60999,约 28K 个可用端口
# 看具体连接方向(本机作为客户端连了多少远端)
ss -ant state time-wait | awk '{print $5}' | awk -F: '{print $1}' | sort | uniq -c | sort -rn | head处理:
bash
# 1. 扩大本地临时端口范围
echo "net.ipv4.ip_local_port_range = 1024 65535" >> /etc/sysctl.d/99-tcp.conf
# 2. 允许 TIME_WAIT 状态的端口被复用(仅对出站连接安全)
echo "net.ipv4.tcp_tw_reuse = 1" >> /etc/sysctl.d/99-tcp.conf
sysctl -p /etc/sysctl.d/99-tcp.conf注意:net.ipv4.tcp_tw_recycle 在 Linux 4.12 已被移除,不要使用——它在 NAT 环境下会导致丢包。tcp_tw_reuse 是安全的替代,且只对出站连接生效。
根本解决:应用改用连接池或长连接,减少短连接的频次。短连接到 Redis、MySQL、上游 HTTP 服务这类场景,连接池(jdbc pool、redis-py 的 connection pool、Nginx 的 keepalive)是必要的。
CLOSE_WAIT 堆积
现象:ss -ant state close-wait 显示某个进程上有大量 CLOSE_WAIT 连接,且数量持续增长不释放。
根因:CLOSE_WAIT 是被动关闭方的状态——对端发了 FIN 表示要关连接,本机内核回了 ACK,但本地应用没有调用 close() 真正关闭 socket。这是应用程序 bug,通常是错误处理路径漏了 close。
判定:
bash
# 看哪个进程持有大量 CLOSE_WAIT
ss -antp state close-wait | awk '{print $NF}' | sort | uniq -c | sort -rn
# 该进程打开的所有 socket
lsof -p <PID> | grep -i tcp处理:这是应用 bug,只能改代码——加上正确的 close 逻辑、用 try-with-resources(Java)/ context manager(Python) / defer close()(Go) 包住 socket 操作。运维侧只能临时通过重启进程缓解,根治需要开发修代码。
CLOSE_WAIT 堆积长期不处理,最终会耗尽进程的文件描述符上限,服务彻底无法接受新连接。
SYN 队列(半连接队列)溢出
现象:客户端报 Connection timed out,服务端 ss -lnt 看到目标端口的 Recv-Q 接近或等于 Send-Q,dmesg 可见:
TCP: request_sock_TCP: Possible SYN flooding on port 80. Sending cookies.根因:三次握手时,服务端收到 SYN 后,把连接信息放入"半连接队列"(SYN_RECV 状态),等待客户端 ACK。这个队列有大小限制(net.ipv4.tcp_max_syn_backlog),满了后新 SYN 被丢弃,表现为客户端连接超时。
可能是真的 SYN flood 攻击,也可能是正常业务流量过大触发。
判定:
bash
# 看半连接数量
ss -ant state syn-recv | wc -l
# 当前限制
cat /proc/sys/net/ipv4/tcp_max_syn_backlog
# 看监听 socket 的队列
ss -lnt | grep ':80'
# State Recv-Q Send-Q Local Address Peer
# LISTEN 128 128 0.0.0.0:80 ...
# Recv-Q 表示当前 SYN_RECV 数量,Send-Q 表示队列上限处理:
bash
# 调大半连接队列上限
echo "net.ipv4.tcp_max_syn_backlog = 65535" >> /etc/sysctl.d/99-tcp.conf
# 启用 SYN cookies(防 SYN flood 的标准手段,默认已开)
echo "net.ipv4.tcp_syncookies = 1" >> /etc/sysctl.d/99-tcp.conf
# 应用层的 backlog 也要调大(Nginx 的 listen 指令)
# server {
# listen 80 backlog=65535;
# }
sysctl -p /etc/sysctl.d/99-tcp.conf应用层 listen() 系统调用传的 backlog 参数,与 tcp_max_syn_backlog 共同决定实际队列大小——内核取两者最小值。所以两边都要调。
MTU 黑洞(PMTU Black Hole)
现象:大请求/大响应失败,小请求成功。例如:
curl http://host/healthzOK(响应几十字节)curl http://host/api/large-data卡死(响应几 MB)- SSH 登录 OK,执行
ls -la输出小没问题,执行cat largefile卡死
根因:MTU 黑洞是 PMTU(Path MTU)发现失败导致的故障。正常 TCP 在握手时会协商 MSS(Maximum Segment Size),如果路径中间某段 MTU 小于协商值,中间设备应该发回 ICMP "Fragmentation Needed",本机收到后调小 MSS 重传。
但很多防火墙错误地把所有 ICMP 一刀切丢弃(包括 PMTU 发现需要的"Fragmentation Needed"消息),导致:
- 小包能过(小于 MTU)
- 大包被中间设备丢弃,且本机收不到通知,只能反复重传至超时
判定:用 ping -M do -s 手工探测路径 MTU:
bash
# 标准以太网 MTU 1500,减去 IP 头(20)和 ICMP 头(8)= 1472 payload
ping -M do -s 1472 -c 3 <目标IP>
# 成功说明路径 MTU >= 1500
ping -M do -s 1400 -c 3 <目标IP>
# 失败说明路径 MTU < 1428(包括头)
# 二分定位准确 MTU典型场景:
- VPN/隧道(IPSec、GRE、WireGuard、VXLAN)会增加额外封装头,MTU 比标准以太网小(常见 1400-1480)
- 跨公网链路某段配了 jumbo frame 而你这边没有
- 容器网络(Flannel VXLAN 模式 MTU 默认 1450)
处理:
bash
# 方式 1:调小本机网卡 MTU 适配最小 MTU
ip link set eth0 mtu 1400
# 方式 2:用 iptables 强制改写 TCP MSS(更精细)
iptables -t mangle -A POSTROUTING -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu
# 方式 3:让中间设备放行 ICMP "Fragmentation Needed"容器/K8s 环境的 MTU 问题:节点物理网卡 MTU 1500,VXLAN 封装后 Pod 网卡应该用 1450。如果 Pod 网络模型设置错(用了 1500),容器内 Pod 互访大包会卡死。
丢包定位的分层方法
业务报"偶发延迟高、间歇丢包",定位哪一层在丢:
网卡层丢包:ip -s link 或 ethtool -S 看 errors / dropped
bash
ip -s link show eth0
# RX: errors > 0 = 物理层错误
# RX: dropped > 0 = 内核来不及处理,ring buffer 满协议栈丢包:/proc/net/snmp 看各种计数器
bash
cat /proc/net/snmp
# InReceives 总收包
# InDiscards 内核丢弃
# RetransSegs TCP 重传次数,持续增长 = 链路丢包netstat -s 看协议层异常:
bash
netstat -s | grep -iE "(retrans|drop|reject|fail)"
# segments retransmitted: 重传段数
# bad segments: 异常段
# resets sent: 发送的 RST应用层丢包:看应用日志的请求成功率,如果应用层都没收到请求 → 网络层丢;如果收到了但响应失败 → 应用层问题。
应用偶发"暂时性失败,几秒后自动好"
现象:应用日志报 Connection reset by peer 或 Connection timed out,但不连续——大约几分钟出现一次,持续几秒,然后恢复。
可能的根因 + 判定:
| 可能 | 判定方式 |
|---|---|
| 上游服务实例滚动重启 | 上游运维确认部署时间 |
| 防火墙状态表 idle 超时清理 | cat /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established(默认 5 天,但中间防火墙可能短得多) |
| 中间网络设备主备切换 | mtr 长跑,看是否有瞬时丢包 |
| 客户端连接池中的连接已被服务端断开,但客户端不知道 | 半开连接,见下一节 |
| 临时端口耗尽 | `ss -ant |
半开连接(half-open)
现象:客户端连接池中的连接看起来正常,但发送请求时 hang 几十秒后报错。
根因:客户端持有的 TCP 连接,服务端那侧已经断了(进程重启、主备切换、防火墙清理),但服务端发的 FIN/RST 没到达客户端(防火墙在中间静默丢弃),客户端不知道连接已死,继续往这个"死"连接上写数据,直到内核 TCP 重传超时。
判定:在客户端机器上抓包,看出去的包是否收到任何 ACK:
bash
tcpdump -i any -nn host <服务端IP> and port <端口>
# 看到本机发了 PSH 但对方完全不回 ACK = 半开处理:
- 应用层启用 keepalive——HTTP client 配置 idle timeout、连接池定期校验连接健康度(
PING探测) - TCP 层 keepalive——
net.ipv4.tcp_keepalive_time默认 7200(2 小时),太长。客户端机器可以调到几分钟:
bash
echo "net.ipv4.tcp_keepalive_time = 300" >> /etc/sysctl.d/99-tcp.conf
echo "net.ipv4.tcp_keepalive_intvl = 30" >> /etc/sysctl.d/99-tcp.conf
echo "net.ipv4.tcp_keepalive_probes = 5" >> /etc/sysctl.d/99-tcp.conf注意 TCP keepalive 默认不是开启状态,需要应用程序在 socket 上调用 setsockopt(SO_KEEPALIVE) 才会启用。Java、Python、Go 的高层 HTTP client 通常需要显式配置。
九、tcpdump:数据包级排查
抓包是最后的手段——前面所有层都看不出问题,且现象明确(具体哪个连接、哪个端口),才上抓包。原因:
- 信息量大,解读耗时
- 生产抓包可能捕获到敏感数据(密码、token)
- 大流量场景下抓包文件迅速膨胀(几分钟几个 GB)
标准用法
bash
tcpdump -i eth0 host 10.0.0.1 # 与某 IP 通信
tcpdump -i eth0 port 80 # 特定端口
tcpdump -i eth0 host 10.0.0.1 and port 443 # 多条件 AND
tcpdump -i eth0 'host 10.0.0.1 and (port 80 or port 443)' # 复合条件
tcpdump -i any -nn port 443 # -i any 所有接口, -nn 不解析端口和主机名
tcpdump -i eth0 -c 100 port 53 # 抓 100 个包后自动停止
tcpdump -i eth0 -w /tmp/debug.pcap host 10.0.0.1 # 保存到文件,Wireshark 分析
tcpdump -r /tmp/debug.pcap # 读取已保存的文件
tcpdump -i eth0 -A port 80 # -A 显示 ASCII 内容(HTTP 明文)
tcpdump -i eth0 -X port 80 # -X 同时显示 hex 和 ASCII生产抓包的关键纪律
必须设过滤条件、必须设上限。不要写无条件的 tcpdump -i any -w file.pcap——几秒钟就是 GB 级文件:
bash
# 限制时间(秒)
timeout 30 tcpdump -i eth0 -w /tmp/debug.pcap host 10.0.0.1
# 限制包数
tcpdump -i eth0 -c 1000 -w /tmp/debug.pcap host 10.0.0.1
# 限制文件大小(MB),滚动覆盖
tcpdump -i eth0 -C 100 -W 5 -w /tmp/debug.pcap host 10.0.0.1
# 单文件 100MB,最多 5 个文件循环,即最多占 500MB解读输出的关键字段
10:23:45.123456 IP 10.0.0.5.34567 > 192.168.1.10.80: Flags [S], seq 1234567890, win 64240, options [mss 1460,sackOK,TS val 123 ecr 0,nop,wscale 7], length 0逐段:
10:23:45.123456— 时间戳(微秒精度)IP— IPv4 协议10.0.0.5.34567 > 192.168.1.10.80— 源 IP.端口 → 目的 IP.端口Flags [S]— TCP 标志位:S=SYN, S.=SYN-ACK, .=ACK, P.=PUSH-ACK, F.=FIN-ACK, R=RSTseq 1234567890— 序列号win 64240— 接收窗口length 0— payload 长度
排查 TCP 握手问题时,看 Flags 序列:
- 客户端 → 服务端:
[S]→ 服务端没回:网络/防火墙问题 - 服务端 → 客户端:
[R](RST):端口未监听 - 服务端 → 客户端:
[S.]但客户端没回[.]:返程路径问题
用 ngrep 做应用层抓包
排查 HTTP 明文流量时,ngrep 比 tcpdump 更直观——它直接显示 ASCII 内容:
bash
yum install ngrep -y
apt install ngrep -y
# 抓所有 80 端口的 HTTP 流量,显示请求/响应内容
ngrep -W byline -d eth0 port 80ngrep 适合排查"HTTP 请求实际发的是什么 / 响应实际是什么",尤其当应用日志记录不完整时。
十、易踩的陷阱与判定速查
| 现象 | 根因 | 直接判定 |
|---|---|---|
| ping 不通,但业务能用 | 对方禁了 ICMP | 用 curl / nc -zv 测业务端口 |
| ping 通,业务不通 | 业务端口被防火墙单独拦截 | nc -zv 或 curl -v |
| Connection refused 立即返回 | 包到对方,端口无服务 | 服务端 ss -lntp |
| Connection timed out 几十秒返回 | 包到不了对方 | mtr 看路径 |
| mtr 中间跳 100% 丢包 | ICMP 限速,中间设备不回应 | 看终点是否丢包,终点正常即无问题 |
dig 出来对,但应用连旧 IP | /etc/hosts 有旧记录 | getent hosts 与 dig 对比 |
| 大文件传输卡死,小请求 OK | 路径 MTU 黑洞 | ping -M do -s 1472 探测 |
| 高并发后无法新建连接 | TIME_WAIT 堆积耗尽本地端口 | ss -s 看连接数,扩大 ip_local_port_range |
| 进程的 CLOSE_WAIT 持续增长 | 应用代码漏 close() | ss -antp state close-wait 看进程 |
| 偶发连接重置,无规律 | 半开连接 / conntrack 超时 / 中间设备超时清理 | 看抓包是否有 RST、是否有未回的 PSH |
| 大量 SYN_RECV | SYN flood 或正常流量打满半连接队列 | tcp_max_syn_backlog 调大、开 syncookies |
| netstat 看不到某连接,ss 能看到 | net-tools 版本旧,某些状态不显示 | 统一用 ss |
curl host:port 报 "no route to host" | 路由层不通(IPv6 优先时常见) | ip route get <IP> 看路由 |