TCP 握手丢包分析:客户端连接超时、服务端无日志的场景排查实践

本文是网络排查案例集 系列的第 1 篇 叙事框架:现象 → 排查过程 → 根因 → 修复 → 预防

问题现象

生产环境某上游服务频繁上报连接超时。抓包分析:在客户端观察到 SYN 报文正常发出,在服务端确认 SYN 已到达且 SYN-ACK 已回复,但客户端侧未捕获到 SYN-ACK。根因定位为防火墙 ACL 规则丢弃了 SYN-ACK 报文。

排查过程

第一步:确认服务端状态

张工先确认服务进程正常运行。

ss -tlnp | grep 8080 确认端口在监听,进程正常运行。但 access 日志中 grep 超时时段的关键字,结果为空------这印证了服务端确实没收到这些请求。

为了看清网络层面发生了什么,张工在服务端启动 tcpdump:

bash 复制代码
tcpdump -i eth0 port 8080 -c 20 -nn -w /tmp/handshake.pcap

等待 15 秒后查看抓包结果,发现了一个关键模式:

bash 复制代码
09:35:12.345678 IP 10.0.1.100.54321 > 10.0.2.200.8080: Flags [S]       ← SYN
09:35:12.345679 IP 10.0.2.200.8080 > 10.0.1.100.54321: Flags [S.]     ← SYN+ACK
09:35:15.345678 IP 10.0.1.100.54321 > 10.0.2.200.8080: Flags [S]       ← SYN 重传
09:35:15.345679 IP 10.0.2.200.8080 > 10.0.1.100.54321: Flags [S.]     ← SYN+ACK 重传
09:35:21.345678 IP 10.0.1.100.54321 > 10.0.2.200.8080: Flags [S]       ← SYN 再次重传
09:35:21.345679 IP 10.0.2.200.8080 > 10.0.1.100.54321: Flags [S.]     ← SYN+ACK 再次重传

模式是:服务端收到了客户端的 SYN,也回复了 SYN+ACK,但第三次握手的 ACK 始终没有来。客户端不断重传 SYN,服务端每次都回应了 SYN+ACK,但连接始终无法建立。

这是一个典型的半开连接场景------服务端认为连接在进行中,但客户端感知不到。

第二步:检查 TCP 连接状态

张工用 ss 查看 TCP 半连接队列:

bash 复制代码
$ ss -t state syn-recv
Recv-Q  Send-Q  Local Address:Port          Peer Address:Port
0       1       10.0.2.200:8080              10.0.1.100.54321
0       1       10.0.2.200:8080              10.0.1.100.54322
0       1       10.0.2.200:8080              10.0.1.100.54323
0       1       10.0.2.200:8080              10.0.1.100.54324
0       1       10.0.2.200:8080              10.0.1.100.54325

5 个连接停留在 SYN-RECV 状态。 这意味着服务端收到了 SYN、发出了 SYN+ACK,但客户端一直没有回复 ACK 来完成三次握手。这些连接正在消耗服务端的 TCP 资源。

进一步用 nstat 查看重传统计:

bash 复制代码
$ nstat -az | grep -E 'Retrans|Loss|Syn'
TcpExtTCPSynRetrans           156             0.0
TcpExtTCPFastRetrans          3               0.0
TcpExtTCPLostRetransmit       12              0.0

156 次 SYN 重传------这是服务端反复回应客户端 SYN 重传所发出的 SYN+ACK 累加数量。

ss -ti 查看每个半连接的详细 TCP 参数:

bash 复制代码
SYN-RECV  0  1  10.0.2.200:8080  10.0.1.100:54321  wscale:7,7 rto:312 rtt:3.5/1.2 ato:40 mss:1460 cwnd:1 bytes_acked:0 segs_out:4 segs_in:4

关键信息:cwnd:1bytes_acked:0segs_out:4------发送了 4 个段但 0 字节被确认,这正是半开连接的典型特征。

第三步:深入分析重传统计

为了确认丢包发生在哪个方向,张工进一步查看了网络层的统计数据:

bash 复制代码
$ netstat -s | grep -E 'retrans|Retrans'
178 segments retransmitted
167 times SYN retransmitted

更重要的是 ethtool -S eth0 的输出:

bash 复制代码
tx_dropped: 167

167 个包在发送时被丢弃,与 167 次 SYN 重传完全吻合。 这说明 SYN+ACK 包在服务端本机的网络栈中就被丢弃了,根本没发到网线上。问题不在网络中间设备,而在本机的网络配置。

第四步:用 Wireshark 视角看清 TCP 握手全过程

为了直观地理解 TCP 三次握手在被 iptables 阻断前后的差异,我们用 Wireshark 风格的包分析工具来看:

正常的三次握手:

  1. Packet 1 --- SYN:客户端(10.0.1.100:54321)→ 服务端(10.0.2.200:8080),Seq=0,Flags=SYN
  2. Packet 2 --- SYN+ACK:服务端 → 客户端,Seq=0,Ack=1,Flags=SYN+ACK
  3. Packet 3 --- ACK:客户端 → 服务端,Seq=1,Ack=1,Flags=ACK

三次握手在 0.126ms 内完成,连接建立。

客户端抓包视角------只有 SYN 发出,没有 SYN+ACK 回来:

从客户端角度看:

  1. SYN 发出(0.000s)
  2. 等待 3s 没有收到 SYN+ACK → 重传 SYN(3.000s)
  3. 再等 6s → 重传 SYN(9.000s)
  4. 最后等 12s → 重传 SYN(21.000s)
  5. 最终 connect() 超时,抛出 Connection timed out

客户端每次重传的间隔呈指数退避(3s → 6s → 12s),这是 TCP 协议的重传机制。如果 SYN+ACK 一直不来,客户端的 connect() 系统调用最终会在超时后返回错误。

服务端抓包视角------SYN+ACK 发出就被丢:

服务端看到的是:客户端的 SYN 每次都到达,服务端每次都回复 SYN+ACK,但回复的包被标识为 "DROPPED by iptables"。服务端的 TCP 栈认为连接还在建立中,所以在 SYN-RECV 队列中维护了这些半连接。

这正是"客户端报超时、服务端说没收到"的完整链路图。

第五步:定位根因------iptables 规则

有了前几步的分析,张工把矛头指向了本机的网络过滤规则。

bash 复制代码
$ iptables -L OUTPUT -n -v --line-numbers
Chain OUTPUT (policy ACCEPT 32567 packets, 12.3M bytes)
num   pkts  bytes  target  prot  opt  in   out   source           destination
1        0      0  ACCEPT  all   --   *    *    0.0.0.0/0        0.0.0.0/0      state RELATED,ESTABLISHED
2     156  12480  DROP    tcp   --   *    *    0.0.0.0/0        0.0.0.0/0      tcp spt:8080 tcpflags: SYN,ACK/SYN,ACK
3    32567  12.3M ACCEPT  all   --   *    *    0.0.0.0/0        0.0.0.0/0

第 2 条规则:DROP tcp spt:8080 tcpflags: SYN,ACK/SYN,ACK

这条规则匹配从 8080 端口发出的、设置了 SYN+ACK 标志位的 TCP 包,然后丢弃。pkts=156,已经丢弃了 156 个 SYN+ACK 包------与前面的 156 次 SYN 重传、167 次 tx_dropped 完美对齐。

这条规则来自上周安全加固的脚本,本意是防 SYN flood 攻击,但配置在 OUTPUT 链上,且匹配条件写反了方向。SYN flood 防护应该在 INPUT 链限制 SYN 包的速率,而不是在 OUTPUT 链丢弃 SYN+ACK 回包。

bash 复制代码
# 正确的做法:在 INPUT 链限制 SYN 速率
iptables -A INPUT -p tcp --dport 8080 --syn -m limit --limit 100/s -j ACCEPT
iptables -A INPUT -p tcp --dport 8080 --syn -j DROP

第六步:修复与验证

张工立即删除了有问题的规则:

bash 复制代码
iptables -D OUTPUT 2

删除后,再次检查 SYN-RECV 队列:

bash 复制代码
$ ss -t state syn-recv
# 空------队列已清空

客户端重试的请求立刻建立连接成功:

bash 复制代码
$ ss -tn sport = :8080 | grep ESTAB
ESTAB  0  0  10.0.2.200:8080  10.0.1.100:54324
ESTAB  0  0  10.0.2.200:8080  10.0.1.100:54325

access 日志中恢复正常流量:

bash 复制代码
10.0.1.100 - - [18/Jun/2025:09:42:15 +0800] "POST /api/callback HTTP/1.1" 200 45
10.0.1.100 - - [18/Jun/2025:09:42:16 +0800] "POST /api/callback HTTP/1.1" 200 45

用 Wireshark 风格工具验证修复后的完整握手:

SYN → SYN+ACK(✅ Delivered)→ ACK(三次握手完成),接着客户端发送 HTTP POST 数据(PSH+ACK),完整的数据传输恢复正常。

根因分析

这次事故的直接原因是 iptables OUTPUT 链上的一条规则错误地丢弃了出站的 SYN+ACK 包。但更深层的问题是:

1. 防 SYN flood 的规则配反了方向

SYN flood 攻击的特征是大量伪造源 IP 的 SYN 包涌入服务端,服务端回复 SYN+ACK 后永远收不到最终的 ACK,导致半连接队列被耗尽。因此 SYN flood 防护应在 INPUT 链 限制 SYN 包的速率,而非在 OUTPUT 链丢弃 SYN+ACK。

这条有问题的规则做了完全相反的事------它限制了正常回包。服务端处理正常客户端的 SYN 请求,回复 SYN+ACK,但 SYN+ACK 被本机 iptables 丢弃。客户端收不到 SYN+ACK,重传 SYN,服务端再次回复 SYN+ACK------再次被丢弃。恶性循环形成。

2. iptables 规则上线前没有 review

安全加固脚本直接推到了生产环境,没有经过 code review 和测试环境验证。iptables 规则的链选择(INPUT vs OUTPUT)、匹配条件(sport vs dport)、目标动作(ACCEPT vs DROP)都需要仔细核对。

3. 监控没有覆盖 TCP 建连阶段

应用层监控只关注了接口响应时间和错误率,但没有拆解 TCP 连接建连阶段的指标。如果当时有 SYN-RECV 队列深度的监控,这个问题在告警出现前就能被发现。

避坑建议

  1. iptables 规则上线前必须 Review :特别是涉及 DROP 操作的规则,确认链方向、端口匹配条件、协议标志位的组合是否符合预期。建议用 iptables -L -n -v 检查规则命中数,上线初期频繁观察 pkts 列是否异常增长。

  2. 防 SYN flood 用专业工具而非手写 iptables :Linux 内核的 sysctl 参数(tcp_syncookiestcp_max_syn_backlogtcp_syn_retries)提供了更成熟的 SYN flood 防护机制。专业场景建议用 nftables、fail2ban 或硬件防火墙。

  3. TCP 建连阶段纳入监控 :SYN-RECV 队列长度、TCP 重传率(nstat -az TcpRetransSegs)应该作为基础网络指标监控。一旦 SYN-RECV 增长或重传率超过阈值,立即告警。

  4. 客户端设置合理的连接超时connect() 超时应根据业务需求设置,通常 5-10s 即可,不必设到 120s。长超时会导致线程资源被长时间占用,在批量超时场景下可能引发线程池耗尽。

  5. tcpdump 双向抓包对比:排查连接问题时,客户端和服务端同时抓包最能定位丢包点。客户端只看到 SYN 发出没收到 SYN+ACK,服务端看到 SYN 和 SYN+ACK 但没有 ACK------方向一目了然。

  6. 安全加固脚本遵循最小权限原则:iptables 默认策略改为 DROP 前,确认已有 ACCEPT 规则覆盖所有必要的流量。逐条测试后再设置默认策略。

附:完整命令清单

bash 复制代码
# 检查端口监听
ss -tlnp | grep 8080

# 查看 TCP 半连接队列
ss -t state syn-recv
ss -tn sport = :8080

# 查看 TCP 详细参数(RTT、cwnd、重传等)
ss -ti state syn-recv

# 查看 TCP 重传统计
nstat -az | grep -E 'Retrans|Loss|Syn'
netstat -s | grep -i retrans

# 查看网卡丢包统计
ethtool -S eth0 | grep -E 'drop|error|retrans'

# iptables 规则检查
iptables -L OUTPUT -n -v --line-numbers

# 删除有问题的规则
iptables -D OUTPUT 2

# 服务端抓包
tcpdump -i eth0 port 8080 -c 20 -nn -w /tmp/handshake.pcap
tcpdump -r /tmp/handshake.pcap -nn

# 筛选特定 TCP 标志位的包
tcpdump -i eth0 'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack != 0' -nn
tcpdump -i eth0 'tcp[tcpflags] & tcp-rst != 0' -nn
相关推荐
白帽小阳2 小时前
Typora插件开发指南:打造专属IDE式写作环境
c语言·网络·python·网络安全·github·pygame·护网行动
网络小白不怕黑2 小时前
13.SSH服务相关配置
服务器·网络·ssh
Jtti2 小时前
站群服务器多IP能不能分散DDoS攻击流量?
服务器·tcp/ip·ddos
科技侃谈2 小时前
曹操充电:以“蚂蚁站“模式破局县域补能,让下沉市场充电网络更轻、更快、更可持续
网络
makise-3 小时前
剑星2026最新版免费下载+修改器
网络·游戏
Bruce_Liuxiaowei3 小时前
一次内网横向移动实战:从一个 5900 端口到穿越防火墙
运维·网络·安全
爱吃大芒果3 小时前
商用门店应用开发:AbilityKit Kiosk 锁屏模式拆解
网络·华为·harmonyos
云计算-Security3 小时前
同(异)主机 Linux NAT 最佳实践
linux·服务器·网络
赛博三把手14 小时前
Claude Fable 5 API 调用完整指南:官方、AWS、OpenRouter 与中转平台对比(2026 最新)
网络·云计算·aws