Mininet 使用笔记(一):三次握手与数据传输
一、准备
1、安装
Ubuntu 24.04
sudo apt update && sudo apt upgrade -y
sudo apt install mininet openvswitch-switch -y
sudo mn --test pingall
# 进入 mn 环境
sudo mn
# 清理 iptables 规则
mininet> h1 iptables -F
mininet> h2 iptables -F
RHEL 8 系列
[root@aliyun ~]# lsb_release -a
LSB Version: :core-4.1-amd64:core-4.1-noarch
Distributor ID: AlibabaCloud
Description: Alibaba Cloud Linux release 3 (OpenAnolis Edition)
Release: 3
Codename: OpenAnolisEdition
# 安装 Mininet 核心依赖
[root@AlibabaCloud ~]# yum install -y gcc make python3 python3-devel python3-setuptools python3-networkx socat psmisc iproute net-tools ethtool help2man
# 创建 Python 软链接(防止底层 C 代码编译报错)
[root@AlibabaCloud ~]# ln -s /usr/bin/python3 /usr/bin/python
# 编译并安装 Mininet
[root@AlibabaCloud ~]# git clone https://github.com/mininet/mininet.git
[root@AlibabaCloud ~]# cd mininet
[root@AlibabaCloud ~]# make install
# 配置环境变量
[root@AlibabaCloud ~]# echo 'export PATH=$PATH:/usr/local/bin' >> ~/.bashrc
[root@AlibabaCloud ~]# source ~/.bashrc
# 启动 Mininet 环境
[root@AlibabaCloud ~]# mn --switch user --controller none
2、Mininet 多终端运行方案
# 主终端
sudo mn
# 方案1:主终端取 h2 的 pid
mininet> dump
<Host h1: h1-eth0:10.0.0.1 pid=1776916>
<Host h2: h2-eth0:10.0.0.2 pid=1776918>
<OVSBridge s1: lo:127.0.0.1,s1-eth1:None,s1-eth2:None pid=1776923>
# 进入 mn h2 终端
sudo mnexec -a 1776918 bash
# 方案2:不进入mn终端内执行
sudo mnexec -a 1776918 tcpdump -l -i h2-eth0 tcp
二、模拟 TCP 通讯各种场景
模拟三次握手场景
h2 = 客户端,h1 = 服务端
握手时序与流向:
第一次握手:h2 → h1 报文标志:SYN
第二次握手:h1 → h2 报文标志:SYN+ACK
第三次握手:h2 → h1 报文标志:纯 ACK
分析 tcpdump 报文流向
场景 1:丢第一次握手 SYN
h2 tcpdump 仅输出:SYN(不断重传)
h1 tcpdump 仅输出:SYN(收到后丢弃,无回复)
确定在客户端 h2 抓包。
场景 2:丢第二次握手 SYN+ACK
h1 tcpdump:SYN(入) → SYN+ACK(出,不断重传)
h2 tcpdump:SYN(出) → SYN+ACK(入,被丢弃)
确定在服务端 h1 抓包。
场景 3:丢第三次握手 ACK
h1 tcpdump:SYN(入) → SYN+ACK(出,重传) → ACK(入,丢弃)
h2 tcpdump:SYN(出) → SYN+ACK(入) → ACK(出,不断重传)
确定在客户端 h2 抓包。
1. 【握手】模拟 SYN 丢包(第1次握手失败)
方案:
- 终端[1] h1 作为服务端
- 终端[2] 抓包客户端 h2 的 TCP 通讯
- 终端[3] h2 作为客户端 但执行顺序是先启动服务端,再抓包,再启动客户端,最后查看抓包结果。
终端[1] h1 作为服务端
sudo mn
# 默认 SYN 重传次数为 6 次
mininet> h2 cat /proc/sys/net/ipv4/tcp_syn_retries
6
# 打印节点信息
mininet> dump
<Host h1: h1-eth0:10.0.0.1 pid=16851>
<Host h2: h2-eth0:10.0.0.2 pid=16853>
<OVSBridge s1: lo:127.0.0.1,s1-eth1:None,s1-eth2:None pid=16858>
# 客户端 h2 发出 SYN 去往 h1 在入口丢弃
mininet> h1 iptables -A INPUT -p tcp --tcp-flags SYN SYN -j DROP
# 启动 HTTP 服务。不启动也可以,反正 h2 的 SYN 不会到达 h1
mininet> h1 python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
终端[2] 监听客户端 h2 eth0 的 TCP 通讯
root@null:/home/null# sudo mnexec -a 16853 tcpdump -l -i h2-eth0 tcp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on h2-eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
终端[3] h2 作为客户端,通过 nc 建立 TCP 请求
nc 请求建立80端口 tcp 连接,150秒超时。
root@null:/home/null# sudo mnexec -a 16853 nc -w 150 10.0.0.1 80
回到 终端[2] 查看监听客户端 h2 的 TCP 通讯结果
13:53:59.057356 IP 10.0.0.2.60312 > 10.0.0.1.http: Flags [S], seq 65972464, win 42340, options [mss 1460,sackOK,TS val 3544669040 ecr 0,nop,wscale 9], length 0
13:54:00.077421 IP 10.0.0.2.60312 > 10.0.0.1.http: Flags [S], seq 65972464, win 42340, options [mss 1460,sackOK,TS val 3544670061 ecr 0,nop,wscale 9], length 0
13:54:01.101436 IP 10.0.0.2.60312 > 10.0.0.1.http: Flags [S], seq 65972464, win 42340, options [mss 1460,sackOK,TS val 3544671085 ecr 0,nop,wscale 9], length 0
13:54:02.125420 IP 10.0.0.2.60312 > 10.0.0.1.http: Flags [S], seq 65972464, win 42340, options [mss 1460,sackOK,TS val 3544672109 ecr 0,nop,wscale 9], length 0
13:54:03.149420 IP 10.0.0.2.60312 > 10.0.0.1.http: Flags [S], seq 65972464, win 42340, options [mss 1460,sackOK,TS val 3544673133 ecr 0,nop,wscale 9], length 0
13:54:04.173419 IP 10.0.0.2.60312 > 10.0.0.1.http: Flags [S], seq 65972464, win 42340, options [mss 1460,sackOK,TS val 3544674157 ecr 0,nop,wscale 9], length 0
13:54:06.221419 IP 10.0.0.2.60312 > 10.0.0.1.http: Flags [S], seq 65972464, win 42340, options [mss 1460,sackOK,TS val 3544676205 ecr 0,nop,wscale 9], length 0
13:54:10.253418 IP 10.0.0.2.60312 > 10.0.0.1.http: Flags [S], seq 65972464, win 42340, options [mss 1460,sackOK,TS val 3544680237 ecr 0,nop,wscale 9], length 0
13:54:18.573440 IP 10.0.0.2.60312 > 10.0.0.1.http: Flags [S], seq 65972464, win 42340, options [mss 1460,sackOK,TS val 3544688557 ecr 0,nop,wscale 9], length 0
13:54:34.957439 IP 10.0.0.2.60312 > 10.0.0.1.http: Flags [S], seq 65972464, win 42340, options [mss 1460,sackOK,TS val 3544704941 ecr 0,nop,wscale 9], length 0
13:55:07.213425 IP 10.0.0.2.60312 > 10.0.0.1.http: Flags [S], seq 65972464, win 42340, options [mss 1460,sackOK,TS val 3544737197 ecr 0,nop,wscale 9], length 0
刚开始模拟就透露出不对劲:
- 客户端持续重传 SYN,证明 h1 的 iptables 规则生效了。
tcp_syn_retries 为 6,但 h2 SYN 重传了 10 次,加上 SYN 第 1 次发送,共发送 SYN 包 11 次; - 抓包结果显示,前4次重传基本是固定1秒;第5条开始按照
指数退避策略重传,每次重试的超时时间翻倍。
分析
按道理来讲,SYN 重传应该按照以下时序进行:
客户端 (Client) 服务端 (Server)
| |
|---------- 1. SYN ---------->| (发送)
| |
| [等待 1s] |
| (未收到 SYN+ACK,触发超时) |
| |
|-------- 2. SYN (重传1) ----->| (RTO = 1s)
| |
| [等待 2s] |
| (未收到 SYN+ACK,触发超时) |
| |
|-------- 3. SYN (重传2) ----->| (RTO = 2s)
| |
| [等待 4s] |
| (未收到 SYN+ACK,触发超时) |
| |
|-------- 4. SYN (重传3) ----->| (RTO = 4s)
| |
| [等待 8s] |
| (未收到 SYN+ACK,触发超时) |
| |
|------- 5. SYN (重传4) ------>| (RTO = 8s)
| |
| [等待 16s] |
| (未收到 SYN+ACK,触发超时) |
| |
|-------- 6. SYN (重传5) ----->| (RTO = 16s)
| |
| [等待 32s] |
| (未收到 SYN+ACK,触发超时) |
| |
|-----------------------------|
| |
| (重传超过 tcp_syn_retries 次,|
| 连接建立失败) |
| |
|=============================|
- 触发条件:客户端发送 SYN 后,在超时时间(RTO, Retransmission Timeout)内未收到服务端的 SYN+ACK 响应。
- 初始超时时间:Linux 系统默认初始 RTO 通常为 1 秒(部分旧系统或特定配置可能为 3 秒)。
- 重试策略:采用指数退避(Exponential Backoff)算法,每次重试的超时时间翻倍。
- 最大重试次数:Linux 默认由 tcp_syn_retries 参数控制,默认值为 6 次。
前 5 次重传间隔约为 1 秒,从第 6 次重传开始,才严格遵循 2、4、8、16、32 秒的指数退避。很显然,问题出在 RTO 上,linux内核引入了对初始 RTO 的平滑处理:
- 初始 RTO 的计算 当客户端首次发送 SYN 时,由于还没有历史往返时间(RTT)的测量数据,内核会使用一个默认的初始 RTO(TCP_RTO_MIN,通常为 200ms)。
- 重传超时(RTO)的更新机制
每次重传后,内核会根据公式更新下一次的超时时间。但在 SYN_SENT 状态下,为了防止初始超时时间过短导致在正常网络波动下频繁误判,内核在更新 RTO 时会受到 TCP_RTO_MIN 的钳制(Clamp)。模拟环境中,TCP_RTO_MIN 为 200ms。
# 最小RTO(微秒) mininet> h1 cat /proc/sys/net/ipv4/tcp_rto_min_us 200000 # 最大RTO(毫秒) mininet> h1 cat /proc/sys/net/ipv4/tcp_rto_max_ms 120000 # 线性重试次数 mininet> h1 cat /proc/sys/net/ipv4/tcp_syn_linear_timeouts 4 - 现象解释 在模拟环境中延迟极低,初始 RTO 计算出来的值可能小于 1 秒。但由于内核机制的限制,前几次重传的 RTO 被“钳制”或“平滑”在了一个固定的最小值(在你的抓包中表现为 1 秒)。直到连续重传次数达到一定阈值(tcp_syn_linear_timeouts 为 4 次),指数退避的乘数效应才完全显现出来,突破了 1 秒的基准线,开始呈现 2s、4s、8s、16s、32s 的翻倍增长。
总结
SYN 总传输次数为 11 次,第 1 次为初始 SYN,后续 4 次为线性重试,第 6-11 次开始指数退避重传。
这种 SYN 重传次数超过 tcp_syn_retries 次的现象是正常的。标准的 TCP 协议规范定义了指数退避机制,但具体的操作系统(如 Linux)在实现时,会根据自身的内核算法(如初始 RTO 的最小值钳制)对前几次重传的等待时间进行微调。此次模拟抓包展示了这一底层优化策略。
2. 【握手】模拟 SYN-ACK 丢包(第2次握手失败)
方案:
- 终端[1] h1 作为服务端
- 终端[2] 抓包服务端 h1 的 TCP 通讯
- 终端[3] h2 作为客户端 但执行顺序是先启动服务端,再抓包,再启动客户端,最后查看抓包结果。
终端[1] h1 作为服务端
# 默认 SYN-ACK 重传次数为 5 次
mininet> h1 cat /proc/sys/net/ipv4/tcp_synack_retries
5
# 清空上次设置的 h1 iptables 丢弃 ACK 的规则
mininet> h1 iptables -F
# 客户端 h2 设置 iptables 丢弃 SYN-ACK
mininet> h2 iptables -A INPUT -p tcp --tcp-flags SYN,ACK SYN,ACK -j DROP
# 启动 HTTP 服务
mininet> h1 python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
终端[2] 监听服务端 h1 eth0 的 TCP 通讯
root@null:/home/null# sudo mnexec -a 16851 tcpdump -l -i h1-eth0 tcp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on h1-eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
终端[3] h2 作为客户端,通过 nc 建立 TCP 请求
nc 请求建立80端口 tcp 连接,150秒超时。
root@null:/home/null# sudo mnexec -a 16853 nc -w 150 10.0.0.1 80
回到 终端[2] 查看监听服务端 h1 的 TCP 通讯结果
13:36:35.560010 IP 10.0.0.2.59060 > 10.0.0.1.http: Flags [S], seq 2384367545, win 42340, options [mss 1460,sackOK,TS val 3630025543 ecr 0,nop,wscale 9], length 0
13:36:35.560017 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 506924313, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219830207 ecr 3630025543,nop,wscale 9], length 0
13:36:36.621423 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 506924313, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219831269 ecr 3630025543,nop,wscale 9], length 0
13:36:36.621439 IP 10.0.0.2.59060 > 10.0.0.1.http: Flags [S], seq 2384367545, win 42340, options [mss 1460,sackOK,TS val 3630026605 ecr 0,nop,wscale 9], length 0
13:36:36.621441 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 506924313, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219831269 ecr 3630025543,nop,wscale 9], length 0
13:36:37.645434 IP 10.0.0.2.59060 > 10.0.0.1.http: Flags [S], seq 2384367545, win 42340, options [mss 1460,sackOK,TS val 3630027629 ecr 0,nop,wscale 9], length 0
13:36:37.645441 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 506924313, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219832293 ecr 3630025543,nop,wscale 9], length 0
13:36:38.669446 IP 10.0.0.2.59060 > 10.0.0.1.http: Flags [S], seq 2384367545, win 42340, options [mss 1460,sackOK,TS val 3630028653 ecr 0,nop,wscale 9], length 0
13:36:38.669450 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 506924313, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219833317 ecr 3630025543,nop,wscale 9], length 0
13:36:39.693436 IP 10.0.0.2.59060 > 10.0.0.1.http: Flags [S], seq 2384367545, win 42340, options [mss 1460,sackOK,TS val 3630029677 ecr 0,nop,wscale 9], length 0
13:36:39.693441 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 506924313, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219834341 ecr 3630025543,nop,wscale 9], length 0
13:36:40.717423 IP 10.0.0.2.59060 > 10.0.0.1.http: Flags [S], seq 2384367545, win 42340, options [mss 1460,sackOK,TS val 3630030701 ecr 0,nop,wscale 9], length 0
13:36:40.717427 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 506924313, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219835365 ecr 3630025543,nop,wscale 9], length 0
13:36:42.765427 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 506924313, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219837413 ecr 3630025543,nop,wscale 9], length 0
13:36:42.765430 IP 10.0.0.2.59060 > 10.0.0.1.http: Flags [S], seq 2384367545, win 42340, options [mss 1460,sackOK,TS val 3630032749 ecr 0,nop,wscale 9], length 0
13:36:42.765434 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 506924313, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219837413 ecr 3630025543,nop,wscale 9], length 0
13:36:46.797355 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 506924313, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219841445 ecr 3630025543,nop,wscale 9], length 0
13:36:46.797360 IP 10.0.0.2.59060 > 10.0.0.1.http: Flags [S], seq 2384367545, win 42340, options [mss 1460,sackOK,TS val 3630036781 ecr 0,nop,wscale 9], length 0
13:36:46.797365 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 506924313, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219841445 ecr 3630025543,nop,wscale 9], length 0
13:36:55.117425 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 506924313, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219849765 ecr 3630025543,nop,wscale 9], length 0
13:36:55.117460 IP 10.0.0.2.59060 > 10.0.0.1.http: Flags [S], seq 2384367545, win 42340, options [mss 1460,sackOK,TS val 3630045101 ecr 0,nop,wscale 9], length 0
13:36:55.117464 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 506924313, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219849765 ecr 3630025543,nop,wscale 9], length 0
13:37:11.501443 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 506924313, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219866149 ecr 3630025543,nop,wscale 9], length 0
13:37:11.501579 IP 10.0.0.2.59060 > 10.0.0.1.http: Flags [S], seq 2384367545, win 42340, options [mss 1460,sackOK,TS val 3630061485 ecr 0,nop,wscale 9], length 0
13:37:11.501585 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 506924313, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219866149 ecr 3630025543,nop,wscale 9], length 0
13:37:43.757572 IP 10.0.0.2.59060 > 10.0.0.1.http: Flags [S], seq 2384367545, win 42340, options [mss 1460,sackOK,TS val 3630093741 ecr 0,nop,wscale 9], length 0
13:37:43.757581 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 1572511246, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219898405 ecr 3630093741,nop,wscale 9], length 0
13:37:44.781419 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 1572511246, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219899429 ecr 3630093741,nop,wscale 9], length 0
13:37:46.829412 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 1572511246, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219901477 ecr 3630093741,nop,wscale 9], length 0
13:37:50.861434 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 1572511246, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219905509 ecr 3630093741,nop,wscale 9], length 0
13:37:59.117325 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 1572511246, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219913765 ecr 3630093741,nop,wscale 9], length 0
13:38:15.501439 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 1572511246, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219930149 ecr 3630093741,nop,wscale 9], length 0
分析
报文有点多,一共32次通讯,让 AI 转成时序图方便分析:
客户端 (10.0.0.2:59060) 服务端 (10.0.0.1:80)
| |
|--- 1. SYN (seq=2384367545) ------------->| [13:36:35.560010]
| | 服务端收到SYN,进入SYN_RECV状态
|<-- 2. SYN-ACK (seq=506924313,ack=2384367546) ----| [13:36:35.560017] (延迟7μs)
| |
| 【核心异常:客户端全程未回复第三次握手纯ACK报文】|
| |
| [客户端等待RTO超时,约1.06s] | [服务端等待ACK超时,约1.06s]
| |
|<--- 3. SYN-ACK 重传1 (seq=506924313) -----| [13:36:36.621423] (+1.06s) 服务端主动重传
|---- 4. SYN 重传1 (seq=2384367545) ------->| [13:36:36.621439] 客户端同步超时重传原始SYN
|<--- 5. SYN-ACK 重传1 (seq=506924313,响应重传SYN)| [13:36:36.621441] 收到重传SYN立刻再回SYN-ACK
| |
| [等待约1.02s,RTO未指数放大] | [等待约1.02s]
| |
|---- 6. SYN 重传2 (seq=2384367545) ------->| [13:36:37.645434] (+1.02s)
|<--- 7. SYN-ACK 重传2 (seq=506924313) -----| [13:36:37.645441] (+1.02s)
| |
| [等待约1.02s] | [等待约1.02s]
| |
|---- 8. SYN 重传3 (seq=2384367545) ------->| [13:36:38.669446] (+1.02s)
|<--- 9. SYN-ACK 重传3 (seq=506924313) -----| [13:36:38.669450] (+1.02s)
| |
| [等待约1.02s] | [等待约1.02s]
| |
|--- 10. SYN 重传4 (seq=2384367545) ------->| [13:36:39.693436] (+1.02s)
|<-- 11. SYN-ACK 重传4 (seq=506924313) -----| [13:36:39.693441] (+1.02s)
| |
| [等待约1.02s] | [等待约1.02s]
| |
|--- 12. SYN 重传5 (seq=2384367545) ------->| [13:36:40.717423] (+1.02s)
|<-- 13. SYN-ACK 重传5 (seq=506924313) -----| [13:36:40.717427] (+1.02s)
| |
| [RTO开始指数退避,等待约2.04s] | [等待约2.04s]
| |
|<--- 14. SYN-ACK 重传6 (seq=506924313) ----| [13:36:42.765427] (+2.04s) 服务端先超时重传
|---- 15. SYN 重传6 (seq=2384367545) ------>| [13:36:42.765430] 客户端同步重传SYN
|<--- 16. SYN-ACK 重传7 (seq=506924313) ----| [13:36:42.765434] 收到SYN再次应答
| |
| [RTO倍增至约4.03s,长时间等待] | [等待约4.03s]
| |
|<--- 17. SYN-ACK 重传8 (seq=506924313) ----| [13:36:46.797355] (+4.03s)
|---- 18. SYN 重传7 (seq=2384367545) ------>| [13:36:46.797360]
|<--- 19. SYN-ACK 重传9 (seq=506924313) ----| [13:36:46.797365]
| |
| [RTO倍增至约8.32s] | [等待约8.32s]
| |
|<--- 20. SYN-ACK 重传10 (seq=506924313) ---| [13:36:55.117425] (+8.32s)
|---- 21. SYN 重传8 (seq=2384367545) ------>| [13:36:55.117460]
|<--- 22. SYN-ACK 重传11 (seq=506924313) ---| [13:36:55.117464]
| |
| [RTO倍增至约16.38s] | [等待约16.38s]
| |
|<--- 23. SYN-ACK 重传12 (seq=506924313) ---| [13:37:11.501443] (+16.38s)
|---- 24. SYN 重传9 (seq=2384367545) ------>| [13:37:11.501579]
|<--- 25. SYN-ACK 重传13 (seq=506924313) ---| [13:37:11.501585]
| |
| [RTO倍增至约32.26s,超长等待] | [等待约32.26s]
| |
|---- 26. SYN 重传10 (seq=2384367545) ----->| [13:37:43.757572] (+32.26s) 客户端最后一次原始SYN
|<--- 27. SYN-ACK 重传14 (seq=1572511246,新ISN) | [13:37:43.757581]
| | 服务端旧半连接超时销毁,新建SYN_RECV上下文,更换序列号
| |
| 【客户端依旧无ACK响应,握手彻底卡死】 |
| |
|<-- 28. SYN-ACK 重传15 (seq=1572511246) ---| [13:37:44.781419] (+1.02s)
|<-- 29. SYN-ACK 重传16 (seq=1572511246) ---| [13:37:46.829412] (+2.04s)
|<-- 30. SYN-ACK 重传17 (seq=1572511246) ---| [13:37:50.861434] (+4.03s)
|<-- 31. SYN-ACK 重传18 (seq=1572511246) ---| [13:37:59.117325] (+8.32s)
|<-- 32. SYN-ACK 重传19 (seq=1572511246) ---| [13:38:15.501439] (+16.38s)
| |
|==========================================|
上面的时序图可以看到:
- SYN 未收到 SYN-ACK 响应,进行了重传(seq=2384367545),重传次数为 10 次,复现了实验1;
- SYN-ACK 未收到客户端的 ACK 响应,也进行了重传;
这里有3个疑问:
- 为什么出现新 SYN-ACK seq(seq=1572511246)?
- 为什么 seq=506924313 的 SYN-ACK 一共传了 15 次,但 seq=1572511246 的 SYN-ACK 只传了 6 次?
- 为什么 seq=506924313 的两条报文 13:36:36.621423 和 13:36:36.621441 间隔仅仅只有 18μs ?
(1) 先解决第2个问题。逐行筛选服务端 [S.] seq=506924313 报文:
13:36:35.560017 初始 SYN+ACK #1
13:36:36.621423 重传 1 #2
13:36:36.621441 重传 1(响应客户端重传 SYN)#3
13:36:37.645441 重传 2 #4
13:36:38.669450 重传 3 #5
13:36:39.693441 重传 4 #6
13:36:40.717427 重传 5 #7
13:36:42.765427 重传 6 #8
13:36:42.765434 重传 6(响应客户端重传 SYN)#9
13:36:46.797355 重传 7 #10
13:36:46.797365 重传 7(响应客户端重传 SYN)#11
13:36:55.117425 重传 8 #12
13:36:55.117464 重传 8(响应客户端重传 SYN)#13
13:37:11.501443 重传 9 #14
13:37:11.501585 重传 9(响应客户端重传 SYN)#15
为什么 seq=506924313 的 SYN-ACK 达到 15 次,而非内核参数 tcp_synack_retries=5?
tcp_synack_retries=5 的真实含义: 内核定时任务主动超时触发的重传最多 5 轮;但如果客户端不断发来新 SYN 报文,服务端收到 SYN 会立刻主动回复一条 SYN+ACK,这类 “被动应答重传” 不受 tcp_synack_retries 计数限制。
- 内核定时超时 → 主动重传,计入 synack_retries 计数,最多 5 轮;
- 客户端重传 SYN 到达服务端 → 内核匹配现有 SYN_RECV 半连接,立即回一条 SYN+ACK,
不占用 synack_retries 配额; - 抓包中客户端每一轮都会持续重传 SYN,每次 SYN 抵达都会触发一条额外 SYN+ACK 被动应答,两种报文叠加后总条数达到 15 条。
- 直到 13:37:43,旧半连接生命周期彻底过期被内核销毁,seq=506924313 报文不再出现。
为什么 seq=1572511246 的 SYN-ACK 传了 6 次?
逐行筛选服务端 [S.] seq=1572511246 报文:
13:37:43.757581 新建半连接初始 SYN+ACK #1
13:37:44.781419 定时超时重传 1 #2
13:37:46.829412 定时超时重传 2 #3
13:37:50.861434 定时超时重传 3 #4
13:37:59.117325 定时超时重传 4 #5
13:38:15.501439 定时超时重传 5 #6
- 旧 ISN=506924313 对应的半连接已超时销毁;13:37:43 客户端再次发送 SYN,服务端无匹配半连接,新建 SYN_RECV 状态,分配全新 ISN=1572511246,发出第一条初始 SYN+ACK;
- 之后仅触发内核定时主动重传,无客户端新 SYN 触发的被动应答报文;
- 服务端tcp_synack_retries=5:初始 1 条 + 最多 5 次定时重传 = 合计 6 条,与抓包条数完全吻合;
- 抓包截止于 13:38:15,刚好完成该半连接全部 5 次定时重传,无更多报文。
核心归纳
- seq=506924313(15 条) 受两种场景叠加:内核定时主动重传(受 synack_retries 限制)+ 客户端 SYN 触发的被动应答重传(无次数上限); 客户端持续高频重传 SYN,每一条 SYN 都会多生成一条 SYN+ACK 回复,叠加后总报文数膨胀至 15 条; 半连接生命周期耗尽后停止输出该 ISN 报文。
- seq=1572511246(6 条) 旧半连接销毁后新建握手上下文,全新 ISN; 全程只有内核定时超时主动重传,无客户端 SYN 触发额外应答; 公式:初始 1 条 SYN+ACK + tcp_synack_retries=5 次定时重传 = 6 条,完全匹配内核参数定义。
- 关键区分点
报文类型 是否计入 tcp_synack_retries 触发条件 定时主动重传 是,最多 5 次 服务端 RTO 超时未收到客户端 ACK SYN 触发被动应答 否,无上限 客户端发来 SYN,匹配到现存 SYN_RECV 半连接
(2) 再解决第1个问题:为什么出现新 SYN-ACK seq(seq=1572511246)?
- 旧半连接销毁条件
服务端参数 tcp_synack_retries=5 控制同一SYN_RECV 半连接的定时重传上限: 同一个四元组(客户端 IP: 端口 + 服务端 IP:80)的半连接,内核定时器最多主动重传 5 轮 SYN+ACK; 超过 5 轮定时重传后,内核判定握手永久失败,直接销毁该 SYN_RECV 半连接,内存中清除旧 ISN=506924313 对应的连接上下文。 - 新 SYN 到达触发新建半连接
13:37:43.757572 客户端依然在发送原始 SYN 报文; 服务端收到 SYN 后遍历半连接哈希表,找不到匹配的 SYN_RECV 条目(旧的已销毁); 内核执行全新握手流程:
- 分配全新随机初始序列号 ISN=1572511246
- 创建新 SYN_RECV 半连接
- 回复第一条 SYN+ACK,从此报文 seq 切换为新值。
(3) 再解决最后一个问题:为什么 seq=506924313 的两条报文 13:36:36.621423 和 13:36:36.621441 间隔仅仅只有 18μs ?
筛选相关报文:
13:36:35.560010 IP 10.0.0.2.59060 > 10.0.0.1.http: Flags [S], seq 2384367545, win 42340, options [mss 1460,sackOK,TS val 3630025543 ecr 0,nop,wscale 9], length 0
13:36:35.560017 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 506924313, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219830207 ecr 3630025543,nop,wscale 9], length 0
13:36:36.621423 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 506924313, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219831269 ecr 3630025543,nop,wscale 9], length 0
13:36:36.621439 IP 10.0.0.2.59060 > 10.0.0.1.http: Flags [S], seq 2384367545, win 42340, options [mss 1460,sackOK,TS val 3630026605 ecr 0,nop,wscale 9], length 0
13:36:36.621441 IP 10.0.0.1.http > 10.0.0.2.59060: Flags [S.], seq 506924313, ack 2384367546, win 43440, options [mss 1460,sackOK,TS val 3219831269 ecr 3630025543,nop,wscale 9], length 0
⏱️ 18μs 时间轴微观还原:
- 13:36:36.621423:服务端RTO 定时任务主动重传 定时器到期,内核协议栈主动发包 SYN+ACK (seq=506924313),属于内核后台定时器触发。
- 13:36:36.621439:客户端 SYN 线性重传 客户端的 SYN 重传定时器也刚好到期,触发了第 1 次线性重传。
- 13:36:36.621441:服务端回复 13:36:36.621439 的 SYN+ACK
时间轴总结
13:36:36.621423 服务端 → 客户端 SYN+ACK (服务端定时器主动重传)
13:36:36.621439 客户端 → 服务端 SYN (客户端定时器主动重传)
13:36:36.621441 服务端 → 客户端 SYN+ACK (服务端收到SYN后的被动立即响应)
总跨度:18μs
总结
- 三次握手缺少第三步ACK,客户端从未回复确认报文;
- 两端各自独立超时重传:客户端反复发原始SYN,服务端反复回SYN-ACK;
- RTO遵循指数退避:1s→2s→4s→8s→16s→32s,重传间隔持续拉长;
- 服务端旧半连接超时清理,更换新ISN重建SYN_RECV,问题无改善;
- 半连接堆积,连接永远无法完成建立,应用层connect/accept永久阻塞。
3. 【握手】模拟 ACK 丢包(第3次握手失败)
方案:
- 终端[1] h1 作为服务端
- 终端[2] 抓包客户端 h2 的 TCP 通讯
- 终端[3] h2 作为客户端 但执行顺序是先启动服务端,再抓包,再启动客户端,最后查看抓包结果。
终端[1] h1 作为服务端
# 在 TCP 的三次握手中,并没有专门针对“第三次握手(ACK)”的重试参数
# 清空上次设置的 h2 iptables 丢弃 SYN-ACK 的规则
mininet> h2 iptables -F
# h1 设置 iptables 丢弃 ACK
mininet> h1 iptables -A INPUT -p tcp --tcp-flags SYN,ACK,FIN,RST ACK -j DROP
# 启动 HTTP 服务
mininet> h1 python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
终端[2] 监听客户端 h2 eth0 的 TCP 通讯
root@null:/home/null# sudo mnexec -a 16853 tcpdump -l -i h2-eth0 tcp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on h2-eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
终端[3] h2 作为客户端,通过 nc 建立 TCP 请求
nc 请求建立80端口 tcp 连接,150秒超时。
root@null:/home/null# sudo mnexec -a 16853 nc -w 150 10.0.0.1 80
回到 终端[2] 查看监听客户端 h2 的 TCP 通讯结果
16:39:23.389742 IP 10.0.0.2.60708 > 10.0.0.1.http: Flags [S], seq 2354081052, win 42340, options [mss 1460,sackOK,TS val 3172064605 ecr 0,nop,wscale 9], length 0
16:39:23.390360 IP 10.0.0.1.http > 10.0.0.2.60708: Flags [S.], seq 2562305839, ack 2354081053, win 43440, options [mss 1460,sackOK,TS val 1856811791 ecr 3172064605,nop,wscale 9], length 0
16:39:23.390392 IP 10.0.0.2.60708 > 10.0.0.1.http: Flags [.], ack 1, win 83, options [nop,nop,TS val 3172064606 ecr 1856811791], length 0
16:39:24.440819 IP 10.0.0.1.http > 10.0.0.2.60708: Flags [S.], seq 2562305839, ack 2354081053, win 43440, options [mss 1460,sackOK,TS val 1856812842 ecr 3172064605,nop,wscale 9], length 0
16:39:24.440834 IP 10.0.0.2.60708 > 10.0.0.1.http: Flags [.], ack 1, win 83, options [nop,nop,TS val 3172065657 ecr 1856811791], length 0
16:39:26.488828 IP 10.0.0.1.http > 10.0.0.2.60708: Flags [S.], seq 2562305839, ack 2354081053, win 43440, options [mss 1460,sackOK,TS val 1856814890 ecr 3172064605,nop,wscale 9], length 0
16:39:26.488846 IP 10.0.0.2.60708 > 10.0.0.1.http: Flags [.], ack 1, win 83, options [nop,nop,TS val 3172067705 ecr 1856811791], length 0
16:39:30.520835 IP 10.0.0.1.http > 10.0.0.2.60708: Flags [S.], seq 2562305839, ack 2354081053, win 43440, options [mss 1460,sackOK,TS val 1856818922 ecr 3172064605,nop,wscale 9], length 0
16:39:30.520852 IP 10.0.0.2.60708 > 10.0.0.1.http: Flags [.], ack 1, win 83, options [nop,nop,TS val 3172071737 ecr 1856811791], length 0
16:39:38.968882 IP 10.0.0.1.http > 10.0.0.2.60708: Flags [S.], seq 2562305839, ack 2354081053, win 43440, options [mss 1460,sackOK,TS val 1856827370 ecr 3172064605,nop,wscale 9], length 0
16:39:38.968900 IP 10.0.0.2.60708 > 10.0.0.1.http: Flags [.], ack 1, win 83, options [nop,nop,TS val 3172080185 ecr 1856811791], length 0
16:39:55.353063 IP 10.0.0.1.http > 10.0.0.2.60708: Flags [S.], seq 2562305839, ack 2354081053, win 43440, options [mss 1460,sackOK,TS val 1856843754 ecr 3172064605,nop,wscale 9], length 0
16:39:55.353087 IP 10.0.0.2.60708 > 10.0.0.1.http: Flags [.], ack 1, win 83, options [nop,nop,TS val 3172096569 ecr 1856811791], length 0
16:41:53.456324 IP 10.0.0.2.60708 > 10.0.0.1.http: Flags [F.], seq 1, ack 1, win 83, options [nop,nop,TS val 3172214672 ecr 1856811791], length 0
16:41:53.457266 IP 10.0.0.1.http > 10.0.0.2.60708: Flags [R], seq 2562305840, win 0, length 0
分析
客户端 h2 (10.0.0.2) 服务端 h1 (10.0.0.1)
| |
|---------- 1. SYN [S] ---------->| t=16:39:23.389
| |
|<-------- 2. SYN+ACK [S.] -------| t=16:39:23.390
| |
|---------- 3. ACK [.] ---------->| t=16:39:23.392(第三次握手 ACK 被 h1 INPUT 丢弃)
| |
| | [h1 收不到 ACK,进入重传计时器]
| |
| [等待约 1.05s] |
|<------- SYN+ACK [S.] (重传 1) ---| t=16:39:24.440
| |
|------------- ACK [.] ---------->| t=16:39:24.441(再次发第三次 ACK,依旧被丢弃)
| |
| | [h1 继续等待 ACK,超时再次重传]
| [等待约 2.05s] |
|<------- SYN+ACK [S.] (重传 2) ---| t=16:39:26.488
| |
|------------- ACK [.] ---------->| t=16:39:26.489(ACK 仍被丢弃)
| |
| [等待约 4.03s] |
|<------- SYN+ACK [S.] (重传 3) ---| t=16:39:30.520
| |
|------------- ACK [.] ---------->| t=16:39:30.521(ACK 仍被丢弃)
| |
| |
| [等待约 8.44s] |
|<------- SYN+ACK [S.] (重传 4) ---| t=16:39:38.968
| |
|------------- ACK [.] ---------->| t=16:39:38.969(ACK 仍被丢弃)
| |
| |
| [等待约 16.38s] |
|<------- SYN+ACK [S.] (重传 5) ---| t=16:39:55.353
| |
|------------- ACK [.] ---------->| t=16:39:55.354(ACK 仍被丢弃)
| |
| |
| [等待长时间无应答,客户端连接超时] |
| |
|---------- FIN [F.] ------------>| t=16:41:53.456 客户端主动发关闭报文
| |
|<---------- RST [R] -------------| t=16:41:53.457 服务端直接重置连接
| |
|=================================|
| 握手始终无法完成,连接最终重置断开 |
|=================================|
时间轴总结
16:39:23.389742:客户端 (10.0.0.2) 向服务端 (10.0.0.1) 发送 SYN 包,请求建立连接。
16:39:23.390360:服务端收到请求,回复 SYN-ACK 包。
16:39:23.390392:客户端回复 ACK 包。注意:此时三次握手理论上已经完成。
16:39:24.440819:至 16:39:55.353063:服务端在发送 SYN-ACK 后,没有收到客户端的 ACK 确认,因此触发了 TCP 重传机制。服务端连续 6 次重传了 SYN-ACK 包,重传间隔呈现指数级增长(约 1s, 2s, 4s, 8.5s, 16.4s)。
16:41:53.456324:客户端在等待约 2 分 30 秒后,主动发送 FIN-ACK 包,单方面关闭了连接。
16:41:53.457266:服务端收到客户端的 FIN 包后,回复了 RST(重置)包,强制终止了该连接。
状态流转链路
# h1(客户端 10.0.0.1)
CLOSED → SYN_RECV → (5 次重传全程保持 SYN_RECV)→ 内核清理半连接(无状态)→ 收到 FIN 回复 RST
# h2(客户端 10.0.0.2)
CLOSED → SYN_SENT → ESTABLISHED(长期停留)→ FIN_WAIT_1 → CLOSED
总结
- 客户端 h2 只要收到 SYN+ACK 就进入 ESTABLISHED,完全不知道自己的 ACK 被丢弃;
- 服务端 h1 收不到第三次 ACK,永久卡在 SYN_RECV,依靠tcp_synack_retries控制重传次数,次数耗尽后销毁半连接;
- 两端连接状态不一致(单侧 ESTABLISHED、单侧 SYN_RECV),属于 TCP 半开连接典型场景。
4. 【正常】模拟 TCP 通信
方案:
- 终端[1] h1 作为服务端
- 终端[2] 抓包客户端 h2 的 TCP 通讯
- 终端[3] h2 作为客户端 但执行顺序是先启动服务端,再抓包,再启动客户端,最后查看抓包结果。
终端[1] h1 作为服务端
# 清空上次设置的 h1 iptables 丢弃 ACK 的规则
mininet> h1 iptables -F
# 启动 HTTP 服务
mininet> h1 python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
终端[2] 监听客户端 h1 eth0 的 TCP 通讯
root@null:/home/null# sudo mnexec -a 16853 tcpdump -l -i h2-eth0 tcp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on h2-eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
终端[3] h2 作为客户端,通过 nc 建立 TCP 请求
nc 请求建立80端口 tcp 连接,5秒超时。
root@null:/home/null# sudo mnexec -a 16853 nc -w 5 10.0.0.1 80 <<EOF
GET / HTTP/1.1
Host: www.jinyazhou.com
Connection: close
EOF
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.12.3
Date: Sat, 20 Jun 2026 09:32:29 GMT
Content-type: text/html; charset=utf-8
Content-Length: 668
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
<li><a href=".bash_history">.bash_history</a></li>
<li><a href=".bashrc">.bashrc</a></li>
<li><a href=".cache/">.cache/</a></li>
<li><a href=".config/">.config/</a></li>
<li><a href=".lesshst">.lesshst</a></li>
<li><a href=".mininet_history">.mininet_history</a></li>
<li><a href=".pip/">.pip/</a></li>
<li><a href=".profile">.profile</a></li>
<li><a href=".pydistutils.cfg">.pydistutils.cfg</a></li>
<li><a href=".ssh/">.ssh/</a></li>
<li><a href="workspace/">workspace/</a></li>
</ul>
<hr>
</body>
</html>
回到 终端[2] 查看监听客户端 h2 的 TCP 通讯结果
17:32:29.000300 IP 10.0.0.2.49472 > 10.0.0.1.http: Flags [S], seq 1046803861, win 42340, options [mss 1460,sackOK,TS val 3175250216 ecr 0,nop,wscale 9], length 0
17:32:29.000801 IP 10.0.0.1.http > 10.0.0.2.49472: Flags [S.], seq 2470724562, ack 1046803862, win 43440, options [mss 1460,sackOK,TS val 1859997401 ecr 3175250216,nop,wscale 9], length 0
17:32:29.000879 IP 10.0.0.2.49472 > 10.0.0.1.http: Flags [.], ack 1, win 83, options [nop,nop,TS val 3175250217 ecr 1859997401], length 0
17:32:29.001089 IP 10.0.0.2.49472 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3175250217 ecr 1859997401], length 58: HTTP: GET / HTTP/1.1
17:32:29.001120 IP 10.0.0.1.http > 10.0.0.2.49472: Flags [.], ack 59, win 85, options [nop,nop,TS val 1859997402 ecr 3175250217], length 0
17:32:29.005356 IP 10.0.0.1.http > 10.0.0.2.49472: Flags [P.], seq 1:156, ack 59, win 85, options [nop,nop,TS val 1859997406 ecr 3175250217], length 155: HTTP: HTTP/1.0 200 OK
17:32:29.005394 IP 10.0.0.2.49472 > 10.0.0.1.http: Flags [.], ack 156, win 83, options [nop,nop,TS val 3175250221 ecr 1859997406], length 0
17:32:29.005427 IP 10.0.0.1.http > 10.0.0.2.49472: Flags [P.], seq 156:824, ack 59, win 85, options [nop,nop,TS val 1859997406 ecr 3175250221], length 668: HTTP
17:32:29.005430 IP 10.0.0.2.49472 > 10.0.0.1.http: Flags [.], ack 824, win 82, options [nop,nop,TS val 3175250221 ecr 1859997406], length 0
17:32:29.005475 IP 10.0.0.1.http > 10.0.0.2.49472: Flags [F.], seq 824, ack 59, win 85, options [nop,nop,TS val 1859997406 ecr 3175250221], length 0
17:32:29.005654 IP 10.0.0.2.49472 > 10.0.0.1.http: Flags [F.], seq 59, ack 825, win 82, options [nop,nop,TS val 3175250221 ecr 1859997406], length 0
17:32:29.005698 IP 10.0.0.1.http > 10.0.0.2.49472: Flags [.], ack 60, win 85, options [nop,nop,TS val 1859997406 ecr 3175250221], length 0
回到 终端[1] 查看服务端打印信息
mininet> h1 python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.0.0.2 - - [20/Jun/2026 17:32:29] "GET / HTTP/1.1" 200 -
分析
客户端 h2 (10.0.0.2) 服务端 h1 (10.0.0.1)
| |
|---------- 1. SYN [S] ---------->| 17:32:29.000300
| |
|<-------- 2. SYN+ACK [S.] -------| 17:32:29.000801
| |
|---------- 3. ACK [.] ---------->| 17:32:29.000879
| | # 三次握手完成,双方进入 ESTABLISHED
| |
|---------- HTTP 请求 [P.] ------->| 17:32:29.001089 GET /
| |
|<------- 应答 ACK [.] ------------| 17:32:29.001120 确认收到客户端请求
| |
|<------- HTTP 响应 [P.] ----------| 17:32:29.005356 200 OK 头部
| |
|---------- ACK [.] ------------->| 17:32:29.005394 确认收到响应头
| |
|<-------- 网页正文 [P.] ----------| 17:32:29.005427 返回页面数据
| |
|---------- ACK [.] ------------->| 17:32:29.005430 确认接收完整数据
| |
|<------- FIN [F.] ---------------| 17:32:29.005475 服务端数据发完,主动关闭出口
| |
|---------- FIN [F.] ------------>| 17:32:29.005654 客户端同意关闭,发送己方 FIN
| |
|<------- ACK [.] ----------------| 17:32:29.005698 服务端确认客户端 FIN
| |
|=================================|
| 流程:握手→HTTP收发→正常四次挥手断开 |
|=================================|
时间轴总结
时间戳 方向 标志位 说明
─────────────────────────────────────────────────────────────
17:32:29.001089 h2 → h1 [P.] h2 发送 HTTP GET 请求
17:32:29.001120 h1 → h2 [.] h1 确认收到请求(纯 ACK)
17:32:29.005356 h1 → h2 [P.] h2 返回 HTTP 响应(200 OK)
17:32:29.005394 h2 → h1 [.] h2 确认收到响应
17:32:29.005427 h2 → h1 [P.] h1 返回页面数据
17:32:29.005430 h1 → h2 [.] h1 确认接收完整数据
17:32:29.005475 h1 → h2 [F.] h1 发起关闭(FIN)
17:32:29.005698 h2 → h1 [.] h2 最终确认(ACK)
TCP 状态转换
| 时间戳 | 报文流向 & 标志 | h2 (客户端) 状态变化 | h1 (服务端) 状态变化 | 说明 |
|---|---|---|---|---|
| 17:32:29.000300 | h2→h1 [S] | CLOSED → SYN_SENT | 保持 CLOSED | 客户端发起第一次握手 SYN |
| 17:32:29.000801 | h1→h2 [S.] | SYN_SENT → ESTABLISHED | CLOSED → SYN_RECV | 服务端回复 SYN+ACK;客户端单方面认为连接就绪 |
| 17:32:29.000879 | h2→h1 [.] | 维持 ESTABLISHED | SYN_RECV → ESTABLISHED | 第三次握手 ACK 送达,两端握手全部完成 |
| 17:32:29.001089 ~ 17:32:29.005430 | [P.] / [.] 数据交互 | 持续 ESTABLISHED | 持续 ESTABLISHED | HTTP 请求、响应、正文双向传输 |
| 17:32:29.005475 | h1→h2 [F.] | 维持 ESTABLISHED | ESTABLISHED → FIN_WAIT_1 | 服务端数据发送完毕,主动发送 FIN 关闭出向通道 |
| 17:32:29.005654 | h2→h1 [F.] | ESTABLISHED → LAST_ACK | FIN_WAIT_1 → FIN_WAIT_2 | 客户端收到 FIN,回复自身 FIN,等待服务端确认 |
| 17:32:29.005698 | h1→h2 [.] | LAST_ACK → CLOSED | FIN_WAIT_2 → TIME_WAIT | 服务端确认客户端 FIN;客户端直接关闭,服务端进入 |
完整状态流转链路
# h1(客户端 10.0.0.1)
CLOSED → SYN_SENT → ESTABLISHED → LAST_ACK → CLOSED
# h2(客户端 10.0.0.2)
CLOSED → SYN_RECV → ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT
总结
这是一个完全正常的 TCP 连接:
- 数据传输成功(h2 发送请求,h1 返回响应)
- 四次挥手完成(双方优雅关闭)
- 没有丢包、没有重传、没有超时
5. 【数据传输】模拟数据包丢包,5s超时
方案:
- 终端[1] h1 作为服务端
- 终端[2] 抓包客户端 h2 的 TCP 通讯
- 终端[3] h2 作为客户端 但执行顺序是先启动服务端,再抓包,再启动客户端,最后查看抓包结果。
终端[1] h1 作为服务端
# ESTABLISHED 阶段,TCP 数据包的重传策略
mininet> h2 cat /proc/sys/net/ipv4/tcp_retries2
15
# 配置 h1 丢弃所有从 10.0.0.2 发送的 80 端口的 PSH 标志位为 1 的 TCP 报文
mininet> h1 iptables -A INPUT -p tcp --tcp-flags PSH PSH -j DROP
# 启动 HTTP 服务
mininet> h1 python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
终端[2] 监听客户端 h1 eth0 的 TCP 通讯
root@null:/home/null# sudo mnexec -a 16853 tcpdump -l -i h2-eth0 tcp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on h2-eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
终端[3] h2 作为客户端,通过 nc 建立 TCP 请求
nc 请求建立80端口 tcp 连接,5秒超时。
root@null:/home/null# sudo mnexec -a 16853 nc -w 5 10.0.0.1 80 <<EOF
GET / HTTP/1.1
Host: www.jinyazhou.com
Connection: close
EOF
回到 终端[2] 查看监听客户端 h2 的 TCP 通讯结果
19:45:51.255170 IP 10.0.0.2.59546 > 10.0.0.1.http: Flags [S], seq 1626166848, win 42340, options [mss 1460,sackOK,TS val 3183252471 ecr 0,nop,wscale 9], length 0
19:45:51.255569 IP 10.0.0.1.http > 10.0.0.2.59546: Flags [S.], seq 180415439, ack 1626166849, win 43440, options [mss 1460,sackOK,TS val 1867999656 ecr 3183252471,nop,wscale 9], length 0
19:45:51.255599 IP 10.0.0.2.59546 > 10.0.0.1.http: Flags [.], ack 1, win 83, options [nop,nop,TS val 3183252471 ecr 1867999656], length 0
19:45:51.255724 IP 10.0.0.2.59546 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3183252471 ecr 1867999656], length 58: HTTP: GET / HTTP/1.1
19:45:51.456880 IP 10.0.0.2.59546 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3183252673 ecr 1867999656], length 58: HTTP: GET / HTTP/1.1
19:45:51.664831 IP 10.0.0.2.59546 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3183252881 ecr 1867999656], length 58: HTTP: GET / HTTP/1.1
19:45:52.072822 IP 10.0.0.2.59546 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3183253289 ecr 1867999656], length 58: HTTP: GET / HTTP/1.1
19:45:52.920823 IP 10.0.0.2.59546 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3183254137 ecr 1867999656], length 58: HTTP: GET / HTTP/1.1
19:45:54.584840 IP 10.0.0.2.59546 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3183255801 ecr 1867999656], length 58: HTTP: GET / HTTP/1.1
19:45:56.260831 IP 10.0.0.2.59546 > 10.0.0.1.http: Flags [F.], seq 59, ack 1, win 83, options [nop,nop,TS val 3183257477 ecr 1867999656], length 0
19:45:56.260877 IP 10.0.0.1.http > 10.0.0.2.59546: Flags [.], ack 1, win 85, options [nop,nop,TS val 1868004662 ecr 3183252471,nop,nop,sack 1 {59:60}], length 0
19:45:56.260894 IP 10.0.0.2.59546 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3183257477 ecr 1868004662], length 58: HTTP: GET / HTTP/1.1
19:45:56.464818 IP 10.0.0.2.59546 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3183257681 ecr 1868004662], length 58: HTTP: GET / HTTP/1.1
19:45:56.872824 IP 10.0.0.2.59546 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3183258089 ecr 1868004662], length 58: HTTP: GET / HTTP/1.1
19:45:57.720806 IP 10.0.0.2.59546 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3183258937 ecr 1868004662], length 58: HTTP: GET / HTTP/1.1
19:45:59.384822 IP 10.0.0.2.59546 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3183260601 ecr 1868004662], length 58: HTTP: GET / HTTP/1.1
19:46:02.648831 IP 10.0.0.2.59546 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3183263865 ecr 1868004662], length 58: HTTP: GET / HTTP/1.1
19:46:09.240861 IP 10.0.0.2.59546 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3183270457 ecr 1868004662], length 58: HTTP: GET / HTTP/1.1
分析
客户端 h2 (10.0.0.2) 服务端 h1 (10.0.0.1)
| |
|---------- 1. SYN [S] ---------->| 19:45:51.255170
| |
|<-------- 2. SYN+ACK [S.] -------| 19:45:51.255569
| |
|---------- 3. ACK [.] ---------->| 19:45:51.255599
| | # 三次握手完成,双方 ESTABLISHED
| |
|--------- HTTP GET [P.] -------->| 19:45:51.255724
| | 【请求抵达 h1,INPUT 规则丢弃,h1 无应答】
| |
| [RTO 超时,指数退避重传] |
|-------- GET 重传 1 [P.] -------->| 19:45:51.456880
| | 【依旧被 h1 丢弃】
| |
|-------- GET 重传 2 [P.] -------->| 19:45:51.664831
| | 【丢弃】
| |
|-------- GET 重传 3 [P.] -------->| 19:45:52.072822
| | 【丢弃】
| |
|-------- GET 重传 4 [P.] -------> | 19:45:52.920823
| | 【丢弃】
| |
|-------- GET 重传 5 [P.] -------->| 19:45:54.584840
| | 【丢弃】
| |
|----------- FIN [F.] ----------->| 19:45:56.260831 客户端准备关闭连接
| |
|<------- SACK ACK [.] -----------| 19:45:56.260877
| | # h1 仅确认握手序列,未确认 HTTP 请求段
| |
|-------- GET 重传 6 [P.] -------->| 19:45:56.260894
| | 【丢弃】
| |
|-------- GET 重传 7 [P.] -------->| 19:45:56.464818
| | 【丢弃】
| |
|-------- GET 重传 8 [P.] -------->| 19:45:56.872824
| | 【丢弃】
| |
|-------- GET 重传 9 [P.] -------->| 19:45:57.720806
| | 【丢弃】
| |
|-------- GET 重传 10 [P.] ------->| 19:45:59.384822
| | 【丢弃】
| |
|-------- GET 重传 11 [P.] ------->| 19:46:02.648831
| | 【丢弃】
| |
|-------- GET 重传 12 [P.] ------->| 19:46:09.240861
| | 【丢弃】
| |
|=================================|
时间轴总结
19:45:51.255170 h2→h1 [S] 客户端发起第一次握手 SYN
19:45:51.255569 h1→h2 [S.] 服务端回复 SYN+ACK 第二次握手
19:45:51.255599 h2→h1 [.] 第三次握手 ACK,两端握手完成
19:45:51.255724 h2→h1 [P.] 发送 HTTP GET 请求,到达 h1 被内核丢弃
19:45:51 ~ 19:46:09:h2 按照 TCP 指数退避规则,持续重复重传 [P.] 请求包,h1 始终无数据 ACK 应答
19:45:56.260831 h2 发送 [F.],客户端等待超时尝试关闭连接
19:45:56.260877 h1 回复 SACK [.],仅确认握手阶段序列号,不确认 HTTP 请求数据段
持续重传至 19:45:56(刚好间隔 5s),达到 nc 空闲超时阈值,客户端 h2 主动发送[F.]发起连接关闭。
TCP 状态转换
| 时间戳 | 报文行为 | h2 (客户端) 状态 | h1 (服务端) 状态 | 说明 |
|---|---|---|---|---|
| 19:45:51.255170 | h2→h1 [S] | CLOSED→SYN_SENT | CLOSED | 发起握手 SYN |
| 19:45:51.255569 | h1→h2 [S.] | SYN_SENT→ESTABLISHED | CLOSED→SYN_RECV | 服务端回复 SYN+ACK |
| 19:45:51.255599 | h2→h1 [.] | ESTABLISHED | SYN_RECV→ESTABLISHED | 第三次 ACK,握手完成 |
| 19:45:51.255724 ~ 19:46:09.240861 | 持续发送 / 重传 [P.] 请求 | 持续 ESTABLISHED | 持续 ESTABLISHED | 请求报文被 h1 INPUT 丢弃,无业务 ACK |
| 19:45:56.260831 | h2 发 [F.] | ESTABLISHED→FIN_WAIT_1 | ESTABLISHED | 客户端等待超时,发起关闭 |
| 19:45:56.260877 | h1 回 SACK [.] | FIN_WAIT_1 | ESTABLISHED | 仅确认握手字节,不确认 HTTP 数据段 |
完整状态流转链路
# h2(客户端 10.0.0.2)
CLOSED → SYN_SENT → ESTABLISHED(长期停留,反复重传请求)→ FIN_WAIT_1
# h1(服务端 10.0.0.1)
CLOSED → SYN_RECV → ESTABLISHED(全程保持,收到请求直接丢弃,无业务应答)
这里有个疑问:在 FIN 之后,GET 重传间隔,为什么又恢复为200ms?
在 19:45:51.255724 发送 GET 请求后,该报文始终未收到完整确认,客户端依次在 19:45:51.456、19:45:51.664、19:45:52.072、19:45:52.920、19:45:54.584 完成多次重传,重传间隔严格遵循 200ms → 400ms → 800ms → 1.6s → 3.2s 的指数退避策略。FIN 发送后 GET 重传间隔重新回落至 200ms 级别,关键转折点为 19:45:56.260877 服务端返回的带 SACK 的 ACK 报文,完整原理如下:
1. 报文 SACK 语义纠正
报文携带 ack 1、SACK {59:60}:ack 1 仅确认握手阶段初始序列号,并未确认 1-59 的 GET 载荷,也未确认序列号 59 的 FIN 报文;SACK 段 {59:60} 作用是告知客户端:序列号 1-59 区间全部丢失,接收端仅空洞区间 59-60 无数据。该报文不属于对 GET/FIN 的有效确认,仅携带丢包空洞提示。
2. 报文带来全新有效 RTT 采样(核心诱因)
本次所有报文均开启 TCP Timestamps 选项,可区分原始报文与重传报文,规避 Karn 算法采样歧义:客户端发送 FIN 后极短时间内收到服务端回包,内核通过 TSval/TSecr 采集到一次全新、极低的往返时延样本,并用该样本更新全局平滑往返时间 SRTT、往返方差 RTTVAR。
3. RTO 重新计算,间隔回落至 200ms
依据 RFC6298 公式 RTO = SRTT + 4×RTTVAR,内核基于新的低时延样本重新计算 RTO,计算结果回落至接近初始基准 200ms。
注意:本次行为不会清零此前的指数退避重传计数(tcp_retries 数值保持不变),只是 RTO 计算值因新 RTT 样本变小,视觉上重传间隔恢复为 200ms;单纯收到部分确认 / SACK 不会直接重置退避计数器、不会强制回退初始 RTO。
4. 机制逻辑总结
并非 “收到部分确认判定网络恢复、暴力重置重传计时器”,而是 TCP 时间戳机制采集到新的低延迟 RTT 样本,更新 SRTT/RTTVAR 后重新算出更小的 RTO,使得后续未确认 GET 段的重传间隔恢复至 200ms 级别;此前多次超时叠加的指数退避计数并未清零,仅单次重传等待时长因新采样数据缩短。
总结
- 握手完全正常,两端进入 ESTABLISHED;
- h2 发出带 PSH 的 HTTP 请求全部到达 h1 网卡,被 iptables 丢弃;
- h1 从未返回 HTTP 应答 ACK,客户端持续指数退避重传请求;
- 重传上限由 /proc/sys/net/ipv4/tcp_retries2 控制;
- nc 进程已触发超时关闭,但 TCP 内核仍会继续完成剩余 TCP 重传逻辑,抓包里 F 之后依旧出现多轮 GET 重传。
应用层 nc 超时优先触发,强行插入 FIN 关闭动作,打断完整的 15 次内核重传序列,不会跑满 tcp_retries2。
6. 【数据传输】模拟数据包丢包,150s超时
方案:
- 终端[1] h1 作为服务端
- 终端[2] 抓包客户端 h2 的 TCP 通讯
- 终端[3] h2 作为客户端 但执行顺序是先启动服务端,再抓包,再启动客户端,最后查看抓包结果。
终端[1] h1 作为服务端
# 最小RTO(微秒)
mininet> h1 cat /proc/sys/net/ipv4/tcp_rto_min_us
200000
# ESTABLISHED 阶段,TCP 数据包的重传策略
mininet> h2 cat /proc/sys/net/ipv4/tcp_retries2
15
# 配置 h1 丢弃所有从 10.0.0.2 发送的 80 端口的 PSH 标志位为 1 的 TCP 报文
mininet> h1 iptables -A INPUT -p tcp --tcp-flags PSH PSH -j DROP
# 启动 HTTP 服务
mininet> h1 python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
为什么选择150秒超时?
初始 RTO = 0.2s,每次翻倍,一共 15 轮重传等待间隔:
第 1 次等待:0.2s
第 2 次等待:0.4s
第 3 次等待:0.8s
第 4 次等待:1.6s
第 5 次等待:3.2s
第 6 次等待:6.4s
第 7 次等待:12.8s
第 8 次等待:25.6s
第 9 次等待:51.2s
第 10 次等待:102.4s
第 11 次等待:204.8s
第 12 次等待:409.6s
第 13 次等待:819.2s
第 14 次等待:1638.4s
第 15 次等待:3276.8s
15 次重传间隔累加总和:
S = 0.2 + 0.4 + 0.8 + ⋯ + 3276.8 = 6553.4 秒 ≈ 109.22 分钟 ≈ 1.82 小时
一般也没有这么长的超时时间,150秒超时足够了。
终端[2] 监听客户端 h1 eth0 的 TCP 通讯
root@null:/home/null# sudo mnexec -a 16853 tcpdump -l -i h2-eth0 tcp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on h2-eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
终端[3] h2 作为客户端,通过 nc 建立 TCP 请求
nc 请求建立80端口 tcp 连接,150秒超时,给足 TCP 完整重传窗口。
root@null:/home/null# sudo mnexec -a 16853 nc -w 150 10.0.0.1 80 <<EOF
GET / HTTP/1.1
Host: www.jinyazhou.com
Connection: close
EOF
回到 终端[2] 查看监听客户端 h2 的 TCP 通讯结果
20:06:21.792733 IP 10.0.0.2.41724 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3184483008 ecr 1869230193], length 58: HTTP: GET / HTTP/1.1
20:06:22.001079 IP 10.0.0.2.41724 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3184483217 ecr 1869230193], length 58: HTTP: GET / HTTP/1.1
20:06:22.208824 IP 10.0.0.2.41724 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3184483425 ecr 1869230193], length 58: HTTP: GET / HTTP/1.1
20:06:22.616812 IP 10.0.0.2.41724 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3184483833 ecr 1869230193], length 58: HTTP: GET / HTTP/1.1
20:06:23.448821 IP 10.0.0.2.41724 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3184484665 ecr 1869230193], length 58: HTTP: GET / HTTP/1.1
20:06:25.112830 IP 10.0.0.2.41724 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3184486329 ecr 1869230193], length 58: HTTP: GET / HTTP/1.1
20:06:28.376818 IP 10.0.0.2.41724 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3184489593 ecr 1869230193], length 58: HTTP: GET / HTTP/1.1
20:06:34.968840 IP 10.0.0.2.41724 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3184496185 ecr 1869230193], length 58: HTTP: GET / HTTP/1.1
20:06:48.280876 IP 10.0.0.2.41724 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3184509497 ecr 1869230193], length 58: HTTP: GET / HTTP/1.1
20:07:14.392823 IP 10.0.0.2.41724 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3184535609 ecr 1869230193], length 58: HTTP: GET / HTTP/1.1
20:08:09.176865 IP 10.0.0.2.41724 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3184590393 ecr 1869230193], length 58: HTTP: GET / HTTP/1.1
20:08:51.882655 IP 10.0.0.2.41724 > 10.0.0.1.http: Flags [F.], seq 59, ack 1, win 83, options [nop,nop,TS val 3184633098 ecr 1869230193], length 0
20:08:51.884045 IP 10.0.0.1.http > 10.0.0.2.41724: Flags [.], ack 1, win 85, options [nop,nop,TS val 1869380284 ecr 3184483008,nop,nop,sack 1 {59:60}], length 0
20:08:51.884103 IP 10.0.0.2.41724 > 10.0.0.1.http: Flags [P.], seq 1:59, ack 1, win 83, options [nop,nop,TS val 3184633100 ecr 1869380284], length 58: HTTP: GET / HTTP/1.1
分析
客户端 h2 (10.0.0.2) 服务端 h1 (10.0.0.1)
| |
|------------ 首包 GET [P.] ---------->| 20:06:21.792733 seq1:59
| | 抵达 h1 被 iptables 丢弃,无数据 ACK 返回
| |
| TCP 指数退避持续重传 GET |
|---------- GET 重传 1 [P.] --------->| 20:06:22.001079
| |
|---------- GET 重传 2 [P.] --------->| 20:06:22.208824
| |
|---------- GET 重传 3 [P.] --------->| 20:06:22.616812
| |
|---------- GET 重传 4 [P.] --------->| 20:06:23.448821
| |
|---------- GET 重传 5 [P.] --------->| 20:06:25.112830
| |
|---------- GET 重传 6 [P.] --------->| 20:06:28.376818
| |
|---------- GET 重传 7 [P.] --------->| 20:06:34.968840
| |
|---------- GET 重传 8 [P.] --------->| 20:06:48.280876
| |
|---------- GET 重传 9 [P.] --------->| 20:07:14.392823
| |
|---------- GET 重传 10 [P.] -------->| 20:08:09.176865
| |
| 达到 nc -w 150s 空闲超时 |
|------------ FIN [F.] ------------->| 20:08:51.882655 seq59
| | 应用关闭套接字,进入 FIN_WAIT_1
|<------- SACK 空洞 ACK [.] ----------| 20:08:51.884045
| | ack=1,SACK {59:60};仅确认握手序列号,标记 1~59 全部丢失
| | 依靠 TCP Timestamp 采集新 RTT,重新计算 RTO
|---------- GET 重传 11 [P.] --------->| 20:08:51.884103
| | 新 RTO 生效,间隔恢复短间隔;下一轮重传间隔极长,抓包无后续报文
TCP 状态转换
| 时间戳 | 报文流向 & 标志 | h2 客户端状态 | h1 服务端状态 | 关键行为说明 |
|---|---|---|---|---|
| 20:06:21.792733 | h2→h1 [P.] | 原始 GET | ESTABLISHED | ESTABLISHED |
| 20:06:22 ~ 20:08:09 | h2→h1 10 轮 [P.] | 重传 ESTABLISHED | ESTABLISHED | 无任何服务端 ACK 反馈,RTO 持续指数翻倍放大,间隔越来越大 |
| 20:08:51.882655 | h2→h1 [F.] | ESTABLISHED → FIN_WAIT_1 | ESTABLISHED | nc 150 秒空闲超时,用户态 close 发送 FIN |
| 20:08:51.884045 | h1→h2 [.] SACK | FIN_WAIT_1 | ESTABLISHED | ack=1 仅确认握手,SACK 标记 1~59 全部丢失;TS 时间戳采样新 RTT,重新计算小 RTO |
| 20:08:51.884103 | h2→h1 [P.] 第 11 次 GET 重传 | FIN_WAIT_1 | ESTABLISHED | 刷新后的小 RTO 生效,发出新一轮 GET 重传;下一次重传间隔极长,抓包无后续报文 |
完整状态流转链路
# h2(客户端 10.0.0.2)
连接建立完成 → ESTABLISHED
持续发送 / 重传 GET 报文,全程保持 ESTABLISHED
20:08:51.882655 发送 FIN:ESTABLISHED → FIN_WAIT_1
收到服务端 SACK ACK,仍保持 FIN_WAIT_1
继续重传未确认 GET 段,直至耗尽 tcp_retries2=15 后内核销毁连接
# h1(服务端 10.0.0.1)
握手完成后进入 ESTABLISHED
持续丢弃客户端 GET [P.] 报文,无数据应答,始终保持 ESTABLISHED
收到客户端 FIN,回复 SACK ACK,依旧维持 ESTABLISHED
总结
- 全程无任何确认 GET 载荷 (seq1:59) 的 ACK,客户端该段始终处于未确认状态;
- FIN 仅代表应用进程退出,不会清除 TCP 内核重传逻辑、不会重置历史退避计数;
- SACK ACK 带来全新 RTT 采样,仅重新计算得到更小 RTO,不会清零之前的超时重传次数;
- 相比-w 5场景,本次 150 秒超时触发 FIN 时 RTO 已放大至几十秒级别,仅能捕获一条刷新后的短间隔重传,无后续密集报文。
参考资料: