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

📅 2026/7/13 12:24:22
TCP 握手丢包分析:客户端连接超时、服务端无日志的场景排查实践
本文是网络排查案例集系列的第 1 篇 叙事框架现象 → 排查过程 → 根因 → 修复 → 预防问题现象生产环境某上游服务频繁上报连接超时。抓包分析在客户端观察到 SYN 报文正常发出在服务端确认 SYN 已到达且 SYN-ACK 已回复但客户端侧未捕获到 SYN-ACK。根因定位为防火墙 ACL 规则丢弃了 SYN-ACK 报文。排查过程第一步确认服务端状态张工先确认服务进程正常运行。ss -tlnp | grep 8080确认端口在监听进程正常运行。但 access 日志中 grep 超时时段的关键字结果为空——这印证了服务端确实没收到这些请求。为了看清网络层面发生了什么张工在服务端启动 tcpdumptcpdump-ieth0 port8080-c20-nn-w/tmp/handshake.pcap等待 15 秒后查看抓包结果发现了一个关键模式09:35:12.345678 IP10.0.1.100.5432110.0.2.200.8080: Flags[S]← SYN 09:35:12.345679 IP10.0.2.200.808010.0.1.100.54321: Flags[S.]← SYNACK 09:35:15.345678 IP10.0.1.100.5432110.0.2.200.8080: Flags[S]← SYN 重传 09:35:15.345679 IP10.0.2.200.808010.0.1.100.54321: Flags[S.]← SYNACK 重传 09:35:21.345678 IP10.0.1.100.5432110.0.2.200.8080: Flags[S]← SYN 再次重传 09:35:21.345679 IP10.0.2.200.808010.0.1.100.54321: Flags[S.]← SYNACK 再次重传模式是服务端收到了客户端的 SYN也回复了 SYNACK但第三次握手的 ACK 始终没有来。客户端不断重传 SYN服务端每次都回应了 SYNACK但连接始终无法建立。这是一个典型的半开连接场景——服务端认为连接在进行中但客户端感知不到。第二步检查 TCP 连接状态张工用ss查看 TCP 半连接队列$ ss-tstate syn-recv Recv-Q Send-Q Local Address:Port Peer Address:Port0110.0.2.200:808010.0.1.100.543210110.0.2.200:808010.0.1.100.543220110.0.2.200:808010.0.1.100.543230110.0.2.200:808010.0.1.100.543240110.0.2.200:808010.0.1.100.543255 个连接停留在 SYN-RECV 状态。这意味着服务端收到了 SYN、发出了 SYNACK但客户端一直没有回复 ACK 来完成三次握手。这些连接正在消耗服务端的 TCP 资源。进一步用nstat查看重传统计$ nstat-az|grep-ERetrans|Loss|SynTcpExtTCPSynRetrans1560.0TcpExtTCPFastRetrans30.0TcpExtTCPLostRetransmit120.0156 次 SYN 重传——这是服务端反复回应客户端 SYN 重传所发出的 SYNACK 累加数量。用ss -ti查看每个半连接的详细 TCP 参数SYN-RECV0110.0.2.200:808010.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:1、bytes_acked:0、segs_out:4——发送了 4 个段但 0 字节被确认这正是半开连接的典型特征。第三步深入分析重传统计为了确认丢包发生在哪个方向张工进一步查看了网络层的统计数据$netstat-s|grep-Eretrans|Retrans178segments retransmitted167timesSYN retransmitted更重要的是ethtool -S eth0的输出tx_dropped:167167 个包在发送时被丢弃与 167 次 SYN 重传完全吻合。这说明 SYNACK 包在服务端本机的网络栈中就被丢弃了根本没发到网线上。问题不在网络中间设备而在本机的网络配置。第四步用 Wireshark 视角看清 TCP 握手全过程为了直观地理解 TCP 三次握手在被 iptables 阻断前后的差异我们用 Wireshark 风格的包分析工具来看正常的三次握手Packet 1 — SYN客户端10.0.1.100:54321→ 服务端10.0.2.200:8080Seq0FlagsSYNPacket 2 — SYNACK服务端 → 客户端Seq0Ack1FlagsSYNACKPacket 3 — ACK客户端 → 服务端Seq1Ack1FlagsACK三次握手在 0.126ms 内完成连接建立。客户端抓包视角——只有 SYN 发出没有 SYNACK 回来从客户端角度看SYN 发出0.000s等待 3s 没有收到 SYNACK → 重传 SYN3.000s再等 6s → 重传 SYN9.000s最后等 12s → 重传 SYN21.000s最终connect()超时抛出Connection timed out客户端每次重传的间隔呈指数退避3s → 6s → 12s这是 TCP 协议的重传机制。如果 SYNACK 一直不来客户端的connect()系统调用最终会在超时后返回错误。服务端抓包视角——SYNACK 发出就被丢服务端看到的是客户端的 SYN 每次都到达服务端每次都回复 SYNACK但回复的包被标识为 “DROPPED by iptables”。服务端的 TCP 栈认为连接还在建立中所以在 SYN-RECV 队列中维护了这些半连接。这正是客户端报超时、服务端说没收到的完整链路图。第五步定位根因——iptables 规则有了前几步的分析张工把矛头指向了本机的网络过滤规则。$ iptables-LOUTPUT-n-v--line-numbers Chain OUTPUT(policy ACCEPT32567packets,12.3M bytes)num pkts bytes target prot optinoutsourcedestination100ACCEPT all -- * *0.0.0.0/00.0.0.0/0 state RELATED,ESTABLISHED215612480DROP tcp -- * *0.0.0.0/00.0.0.0/0 tcp spt:8080 tcpflags: SYN,ACK/SYN,ACK33256712.3M ACCEPT all -- * *0.0.0.0/00.0.0.0/0第 2 条规则DROP tcp spt:8080 tcpflags: SYN,ACK/SYN,ACK这条规则匹配从 8080 端口发出的、设置了 SYNACK 标志位的 TCP 包然后丢弃。pkts156已经丢弃了 156 个 SYNACK 包——与前面的 156 次 SYN 重传、167 次 tx_dropped 完美对齐。这条规则来自上周安全加固的脚本本意是防 SYN flood 攻击但配置在 OUTPUT 链上且匹配条件写反了方向。SYN flood 防护应该在 INPUT 链限制 SYN 包的速率而不是在 OUTPUT 链丢弃 SYNACK 回包。# 正确的做法在 INPUT 链限制 SYN 速率iptables-AINPUT-ptcp--dport8080--syn-mlimit--limit100/s-jACCEPT iptables-AINPUT-ptcp--dport8080--syn-jDROP第六步修复与验证张工立即删除了有问题的规则iptables-DOUTPUT2删除后再次检查 SYN-RECV 队列$ ss-tstate syn-recv# 空——队列已清空客户端重试的请求立刻建立连接成功$ ss-tnsport:8080|grepESTAB ESTAB0010.0.2.200:808010.0.1.100:54324 ESTAB0010.0.2.200:808010.0.1.100:54325access 日志中恢复正常流量10.0.1.100 - -[18/Jun/2025:09:42:15 0800]POST /api/callback HTTP/1.12004510.0.1.100 - -[18/Jun/2025:09:42:16 0800]POST /api/callback HTTP/1.120045用 Wireshark 风格工具验证修复后的完整握手SYN → SYNACK✅ Delivered→ ACK三次握手完成接着客户端发送 HTTP POST 数据PSHACK完整的数据传输恢复正常。根因分析这次事故的直接原因是 iptables OUTPUT 链上的一条规则错误地丢弃了出站的 SYNACK 包。但更深层的问题是1. 防 SYN flood 的规则配反了方向SYN flood 攻击的特征是大量伪造源 IP 的 SYN 包涌入服务端服务端回复 SYNACK 后永远收不到最终的 ACK导致半连接队列被耗尽。因此 SYN flood 防护应在INPUT 链限制 SYN 包的速率而非在 OUTPUT 链丢弃 SYNACK。这条有问题的规则做了完全相反的事——它限制了正常回包。服务端处理正常客户端的 SYN 请求回复 SYNACK但 SYNACK 被本机 iptables 丢弃。客户端收不到 SYNACK重传 SYN服务端再次回复 SYNACK——再次被丢弃。恶性循环形成。2. iptables 规则上线前没有 review安全加固脚本直接推到了生产环境没有经过 code review 和测试环境验证。iptables 规则的链选择INPUT vs OUTPUT、匹配条件sport vs dport、目标动作ACCEPT vs DROP都需要仔细核对。3. 监控没有覆盖 TCP 建连阶段应用层监控只关注了接口响应时间和错误率但没有拆解 TCP 连接建连阶段的指标。如果当时有 SYN-RECV 队列深度的监控这个问题在告警出现前就能被发现。避坑建议iptables 规则上线前必须 Review特别是涉及 DROP 操作的规则确认链方向、端口匹配条件、协议标志位的组合是否符合预期。建议用iptables -L -n -v检查规则命中数上线初期频繁观察 pkts 列是否异常增长。防 SYN flood 用专业工具而非手写 iptablesLinux 内核的sysctl参数tcp_syncookies、tcp_max_syn_backlog、tcp_syn_retries提供了更成熟的 SYN flood 防护机制。专业场景建议用 nftables、fail2ban 或硬件防火墙。TCP 建连阶段纳入监控SYN-RECV 队列长度、TCP 重传率nstat -az TcpRetransSegs应该作为基础网络指标监控。一旦 SYN-RECV 增长或重传率超过阈值立即告警。客户端设置合理的连接超时connect()超时应根据业务需求设置通常 5-10s 即可不必设到 120s。长超时会导致线程资源被长时间占用在批量超时场景下可能引发线程池耗尽。tcpdump 双向抓包对比排查连接问题时客户端和服务端同时抓包最能定位丢包点。客户端只看到 SYN 发出没收到 SYNACK服务端看到 SYN 和 SYNACK 但没有 ACK——方向一目了然。安全加固脚本遵循最小权限原则iptables 默认策略改为 DROP 前确认已有 ACCEPT 规则覆盖所有必要的流量。逐条测试后再设置默认策略。附完整命令清单# 检查端口监听ss-tlnp|grep8080# 查看 TCP 半连接队列ss-tstate syn-recv ss-tnsport:8080# 查看 TCP 详细参数RTT、cwnd、重传等ss-tistate syn-recv# 查看 TCP 重传统计nstat-az|grep-ERetrans|Loss|Synnetstat-s|grep-iretrans# 查看网卡丢包统计ethtool-Seth0|grep-Edrop|error|retrans# iptables 规则检查iptables-LOUTPUT-n-v--line-numbers# 删除有问题的规则iptables-DOUTPUT2# 服务端抓包tcpdump-ieth0 port8080-c20-nn-w/tmp/handshake.pcap tcpdump-r/tmp/handshake.pcap-nn# 筛选特定 TCP 标志位的包tcpdump-ieth0tcp[tcpflags] tcp-syn ! 0 and tcp[tcpflags] tcp-ack ! 0-nntcpdump-ieth0tcp[tcpflags] tcp-rst ! 0-nn