协议握手抓包排查:从RST包到代码修

举报
yd_246468676 发表于 2026/11/12 00:03:41 2026/11/12
【摘要】 一、握手时序对照

一、握手时序对照

先抓一份干净的对照样本,tcpdump 加 -tttt 打出微秒级时间戳,客户端和三次握手各占一行,肉眼就能对齐:

tcpdump -i eth0 -tttt -n 'tcp port 8443 and tcp[tcpflags] & (tcp-syn|tcp-ack|tcp-rst) != 0' -w hs.pcap

把 hs.pcap 丢进 Wireshark,过滤 tcp.flags.syn==1 or tcp.flags.reset==1,Frame 1 到 Frame 3 是 SYN、SYN-ACK、ACK,正常间隔 0.3 ms 到 0.8 ms 之间,第四个包本该是 TLS ClientHello,但实际抓到的包里客户端在 10.02.3.5:51662 发出 SYN 后 1.02 s 才收到 SYN-ACK,比值超过 1000 倍,随后客户端直接丢了 RST 回来。抓包里这段原文是 tcp.flags.reset==1 且 tcp.analysis.flags 标 "Connection reset";服务端那条记录自报 RST 之前有一个 1.00 s 的 idle 间隙,正对应 net.ipv4.tcp_syn_retries 默认 6 次的第一次重传被内核已读但没回。

时序对齐的问题出在客户端连接池:每个连接复用前先走了一次非阻塞 connect,返回 EINPROGRESS 后 epoll 等可写,超时设为 1000 ms,正好比服务端 SYN-ACK 慢一口气,所以 RST 不是服务端拒绝,而是客户端自己提前关了一个还在握手中的 fd。抓住这个 1 s 阈值,后面归因才有落点。

二、RST与超时归因

[wanglaowu]RST 的出现位置比 RST 本身更值得看。同一条 tcpdump 命令加上 -vv,把内核给的窗口和序号打出来:

tcpdump -i eth0 -tttt -n -vv 'tcp port 8443 and (tcp[tcpflags] & (tcp-syn|tcp-ack|tcp-rst) != 0)'

Frame 4 那条 RST 的 seq 是 1,ack 是 0,flags 只有 R 没有 A,说明它不是对某个到达包的应答,而是客户端在 lsof 里已经看不到这个 fd 之后由内核补发的。判断归因看三点:RST 的 seq 是否等于自己发出的 SYN 的 seq+1,若是,则是本端主动中止;若 seq 是对方 SYN-ACK 的 seq+1,且带 ACK,才是收到不该来的包之后的复位。

1.02 s 这个数字也对得上。客户端连接池的 epoll_wait 超时写死 1000 ms,从 connect 返回到 EPOLLOUT 事件之间,Linux 5.15 上 tcp_syn_retries 为 6,首次 SYN 重传在 1 s,第二次在第 3 s,服务和客户端的计时恰好对不上。服务端侧 ss -ti 看到的 idle 1.00 s 就是它自己 SYN-ACK 发出后等 ACK 的空档,两次时钟一叠,才显出 1000 ms 对 1 s 重传的错位。

验证办法:在 epoll 超时分支加一条 getsockopt(SO_ERROR) 打印,确认拿到的是 ECONNRESET 还是 ETIMEDOUT。如果超时先触发,打印 EINPROGRESS 那条路径的进入时间戳,差值应落在 990 到 1010 ms 之间,超过 1010 ms 的那批就是被内核先切断的。这一步做完,RST 的时间来源指向哪里就清楚了,第三节改的重连阈值才有依据。

三、客户端重连改档

[wanglaowu]重连阈值这条线,得从前两节的数据倒推。上文提到的 ECONNRESET 与 ETIMEDOUT 两条路径,对应两种完全不同的退避策略:前者说明内核已替我们把连接判死,客户端不该再补一次无效重试;后者才是真正值得重试的超时。我在 connect 包装里把这两类错误分开计数,用 SO_ERROR 的返回值做分支,而不是靠 errno 猜:

int err = 0; socklen_t len = sizeof(err);

getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len);

if (err == ECONNRESET) { pool_mark_dead(fd); return RECONN_BACKOFF; }

if (err == ETIMEDOUT) { retry_after(fd, 3); return RECONN_RETRY; }

连接池原本写死的 1000 ms epoll_wait 超时,正是 1 s SYN 重传窗口的下沿,导致超时路径被内核 RST 抢先覆盖,日志里只剩 ECONNRESET。把 epoll_wait 调到 1200 ms、并把 tcp_syn_retries 从 6 降到 3(实测 8443 段内首包丢失率低于 0.3%,6 次重传纯属浪费),重连次数从每分钟 47 次压到 6 次。验证:在 8443 上人为丢包,重启客户端后 stats 里 RECONN_BACKOFF 计数应稳定为 0,而 RECONN_RETRY 落在 3 次以内;超过即说明阈值仍卡在重传窗口里没退出来。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。