金亚洲技术笔记

凡是过往,皆为序章。

Mininet 使用笔记(一):三次握手与数据传输

Posted on   » 网络通信 • 13869 words • 28 minute read
Tags: 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个疑问:

  1. 为什么出现新 SYN-ACK seq(seq=1572511246)?
  2. 为什么 seq=506924313 的 SYN-ACK 一共传了 15 次,但 seq=1572511246 的 SYN-ACK 只传了 6 次?
  3. 为什么 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 计数限制。

  1. 内核定时超时 → 主动重传,计入 synack_retries 计数,最多 5 轮;
  2. 客户端重传 SYN 到达服务端 → 内核匹配现有 SYN_RECV 半连接,立即回一条 SYN+ACK,不占用 synack_retries 配额
  3. 抓包中客户端每一轮都会持续重传 SYN,每次 SYN 抵达都会触发一条额外 SYN+ACK 被动应答,两种报文叠加后总条数达到 15 条。
  4. 直到 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
  1. 旧 ISN=506924313 对应的半连接已超时销毁;13:37:43 客户端再次发送 SYN,服务端无匹配半连接,新建 SYN_RECV 状态,分配全新 ISN=1572511246,发出第一条初始 SYN+ACK;
  2. 之后仅触发内核定时主动重传,无客户端新 SYN 触发的被动应答报文;
  3. 服务端tcp_synack_retries=5:初始 1 条 + 最多 5 次定时重传 = 合计 6 条,与抓包条数完全吻合;
  4. 抓包截止于 13:38:15,刚好完成该半连接全部 5 次定时重传,无更多报文。
核心归纳
  1. seq=506924313(15 条) 受两种场景叠加:内核定时主动重传(受 synack_retries 限制)+ 客户端 SYN 触发的被动应答重传(无次数上限); 客户端持续高频重传 SYN,每一条 SYN 都会多生成一条 SYN+ACK 回复,叠加后总报文数膨胀至 15 条; 半连接生命周期耗尽后停止输出该 ISN 报文。
  2. seq=1572511246(6 条) 旧半连接销毁后新建握手上下文,全新 ISN; 全程只有内核定时超时主动重传,无客户端 SYN 触发额外应答; 公式:初始 1 条 SYN+ACK + tcp_synack_retries=5 次定时重传 = 6 条,完全匹配内核参数定义。
  3. 关键区分点
    报文类型 是否计入 tcp_synack_retries 触发条件
    定时主动重传 是,最多 5 次 服务端 RTO 超时未收到客户端 ACK
    SYN 触发被动应答 否,无上限 客户端发来 SYN,匹配到现存 SYN_RECV 半连接

(2) 再解决第1个问题:为什么出现新 SYN-ACK seq(seq=1572511246)?

  1. 旧半连接销毁条件
    服务端参数 tcp_synack_retries=5 控制同一SYN_RECV 半连接的定时重传上限: 同一个四元组(客户端 IP: 端口 + 服务端 IP:80)的半连接,内核定时器最多主动重传 5 轮 SYN+ACK; 超过 5 轮定时重传后,内核判定握手永久失败,直接销毁该 SYN_RECV 半连接,内存中清除旧 ISN=506924313 对应的连接上下文。
  2. 新 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 时间轴微观还原:

  1. 13:36:36.621423:服务端RTO 定时任务主动重传 定时器到期,内核协议栈主动发包 SYN+ACK (seq=506924313),属于内核后台定时器触发。
  2. 13:36:36.621439:客户端 SYN 线性重传 客户端的 SYN 重传定时器也刚好到期,触发了第 1 次线性重传。
  3. 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
总结
  1. 三次握手缺少第三步ACK,客户端从未回复确认报文;
  2. 两端各自独立超时重传:客户端反复发原始SYN,服务端反复回SYN-ACK;
  3. RTO遵循指数退避:1s→2s→4s→8s→16s→32s,重传间隔持续拉长;
  4. 服务端旧半连接超时清理,更换新ISN重建SYN_RECV,问题无改善;
  5. 半连接堆积,连接永远无法完成建立,应用层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 级别;此前多次超时叠加的指数退避计数并未清零,仅单次重传等待时长因新采样数据缩短。

总结
  1. 握手完全正常,两端进入 ESTABLISHED;
  2. h2 发出带 PSH 的 HTTP 请求全部到达 h1 网卡,被 iptables 丢弃;
  3. h1 从未返回 HTTP 应答 ACK,客户端持续指数退避重传请求;
  4. 重传上限由 /proc/sys/net/ipv4/tcp_retries2 控制;
  5. 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
总结
  1. 全程无任何确认 GET 载荷 (seq1:59) 的 ACK,客户端该段始终处于未确认状态;
  2. FIN 仅代表应用进程退出,不会清除 TCP 内核重传逻辑、不会重置历史退避计数;
  3. SACK ACK 带来全新 RTT 采样,仅重新计算得到更小 RTO,不会清零之前的超时重传次数;
  4. 相比-w 5场景,本次 150 秒超时触发 FIN 时 RTO 已放大至几十秒级别,仅能捕获一条刷新后的短间隔重传,无后续密集报文。

参考资料:

  1. 《Linux TCP_RTO_MIN, TCP_RTO_MAX and the tcp_retries2 sysctl》
  2. 《TCP/IP卷1学习:TCP 重传超时(RTO)的 Linux 实现方法详解》
  3. 《深入剖析阻塞式socket的timeout》
  4. 《net.ipv4.tcp_retries2作用介绍》
×