文章总结: 本文通过packetdrill脚本实验深入分析TCP慢启动机制,重点研究RTO超时重传场景下拥塞窗口(cwnd)和慢启动阈值(ssthresh)的变化规律。实验发现:首次RTO超时后cwnd重置为1,ssthresh减半;同一数据包多次RTO超时不会进一步降低ssthresh;但不同数据包相继发生RTO超时会导致ssthresh持续减半。文章详细展示了TCP_CA_Loss状态转换过程,为理解Linux内核TCP拥塞控制状态机提供了实测依据。 综合评分: 78 文章分类: 网络协议,漏洞分析,安全工具,安全运营,其他
Wireshark & Packetdrill | TCP 慢启动(6)
原创
7ACE 7ACE
Echo Reply
2026年2月9日 08:09 江苏
持续思索,探究根本原因
实验目的
基于 packetdrill TCP 三次握手脚本,通过构造模拟服务器端场景,继续研究测试 TCP 慢启动现象。
基础脚本
# cat tcp_tcp_slow_start_000.pkt 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0+0 bind(3, ..., ...) = 0+0 listen(3, 1) = 0
+0 < S 0:0(0) win 10000 <mss 1460>+0 > S. 0:0(0) ack 1 <...>+0.01 < . 1:1(0) ack 1 win 10000+0 accept(3, ..., ...) = 4#
#
慢启动
TCP 慢启动是 TCP 拥塞控制的一种初始机制,其核心目的是在连接建立或重传超时后,快速而谨慎地探测网络的可用带宽。发送方通过维护一个拥塞窗口(cwnd)来控制未确认数据量,在慢启动阶段,每收到一个确认数据包(ACK),cwnd 就增加一个最大报文段长度(MSS)。这使得在一个往返时延(RTT)内,能发送的数据量大约会翻倍,从而实现窗口的指数级增长,直至达到慢启动阈值(ssthresh)或检测到数据包丢失,此后连接将转入线性增长的拥塞避免阶段。
在 Linux 实现中,慢启动过程会在三种典型场景下触发:连接建立时(默认 cwnd=10、ssthresh 为极大值),确保必然进入慢启动(除非路由表配置覆盖);RTO 超时重传时将 cwnd 重置为1,同时根据拥塞控制算法重新计算 ssthresh 后重新启动慢启动;连接空闲超过 RTO 时间时也会重新初始化拥塞窗口,通过慢启动重新探测网络状态。这三种机制共同保障 TCP 在不同场景下都能合理适配网络容量。
注:TCP 协议极其复杂,在具体实现上也有很多变化,因此有些概念阐述得比较难理解,它是有一定前提条件和背景的。
基础测试
在上述慢启动场景中,有一种是发生 RTO 超时重传后重新启动慢启动的场景,其中会涉及到 cwnd 和 ssthresh 的值的变化,此次简单测试下不同 RTO 超时重传场景下包括 TCP 拥塞控制状态机以及 cwnd、ssthresh 值变化的情况。
初始基础场景的脚本如下,运行后输出 TCP 拥塞控制状态为 TCP_CA_Open、cwnd 初始值 10 以及 ssthresh 初始值 2147483647。
# cat tcp_slow_start_1_019.pkt 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0+0 bind(3, ..., ...) = 0+0 listen(3, 1) = 0
+0 < S 0:0(0) win 10000 <mss 1000,nop,nop,sackOK>+0 > S. 0:0(0) ack 1 <...>+0.01 < . 1:1(0) ack 1 win 10000+0 accept(3, ..., ...) = 4
+0.01 %{print (tcpi_ca_state, tcpi_snd_cwnd, tcpi_snd_ssthresh)}%#
# packetdrill tcp_slow_start_1_019.pkt 0 10 2147483647#
修改脚本,增加发送数据且发生第一次 RTO 超时重传的情形,如下。
# cat tcp_slow_start_1_020.pkt 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0+0 bind(3, ..., ...) = 0+0 listen(3, 1) = 0
+0 < S 0:0(0) win 10000 <mss 1000,nop,nop,sackOK>+0 > S. 0:0(0) ack 1 <...>+0.01 < . 1:1(0) ack 1 win 10000+0 accept(3, ..., ...) = 4
+0.01 %{print (tcpi_ca_state, tcpi_snd_cwnd, tcpi_snd_ssthresh)}%
+0.01 write(4,...,1000) = 1000
+0.5 %{print (tcpi_ca_state, tcpi_snd_cwnd, tcpi_snd_ssthresh)}%#
运行脚本,并通过 tcpdump 捕获数据包如下,TCP 拥塞控制状态由 TCP_CA_Open 变为 TCP_CA_Loss、cwnd 值由 10 变为 1、ssthresh 值由 2147483647 变为 7。
# packetdrill tcp_slow_start_1_020.pkt 0 10 21474836474 1 7#
# tcpdump -i any -nn port 8080tcpdump: data link type LINUX_SLL2tcpdump: verbose output suppressed, use -v[v]... for full protocol decodelistening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes22:02:21.247965 tun0 In IP 192.0.2.1.56619 > 192.168.140.79.8080: Flags [S], seq 0, win 10000, options [mss 1000,nop,nop,sackOK], length 022:02:21.247986 tun0 Out IP 192.168.140.79.8080 > 192.0.2.1.56619: Flags [S.], seq 339164161, ack 1, win 64240, options [mss 1460,nop,nop,sackOK], length 022:02:21.258080 tun0 In IP 192.0.2.1.56619 > 192.168.140.79.8080: Flags [.], ack 1, win 10000, length 022:02:21.278238 tun0 Out IP 192.168.140.79.8080 > 192.0.2.1.56619: Flags [P.], seq 1:1001, ack 1, win 64240, length 1000: HTTP22:02:21.491464 tun0 Out IP 192.168.140.79.8080 > 192.0.2.1.56619: Flags [P.], seq 1:1001, ack 1, win 64240, length 1000: HTTP22:02:21.707470 tun0 Out IP 192.168.140.79.8080 > 192.0.2.1.56619: Flags [P.], seq 1:1001, ack 1, win 64240, length 1000: HTTP22:02:21.840425 ? Out IP 192.168.140.79.8080 > 192.0.2.1.56619: Flags [F.], seq 1001, ack 1, win 64240, length 022:02:21.840450 ? In IP 192.0.2.1.56619 > 192.168.140.79.8080: Flags [R.], seq 1, ack 1, win 10000, length 0^C8 packets captured9 packets received by filter0 packets dropped by kernel#
注,Seq 1:1001 初始发送后,第一次看到的重传为 TLP ,第二次才是实际的 RTO 超时重传。
如果同一个数据包再次发生了超时重传呢,cwnd 值还是 1 ,ssthresh 值是否还会变化。基于此问题修改脚本,再增加一次 RTO 超时重传,如下。
# cat tcp_slow_start_1_021.pkt 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0+0 bind(3, ..., ...) = 0+0 listen(3, 1) = 0
+0 < S 0:0(0) win 10000 <mss 1000,nop,nop,sackOK>+0 > S. 0:0(0) ack 1 <...>+0.01 < . 1:1(0) ack 1 win 10000+0 accept(3, ..., ...) = 4
+0.01 %{print (tcpi_ca_state, tcpi_snd_cwnd, tcpi_snd_ssthresh)}%
+0.01 write(4,...,1000) = 1000
+1 %{print (tcpi_ca_state, tcpi_snd_cwnd, tcpi_snd_ssthresh)}%#
运行脚本,并通过 tcpdump 捕获数据包如下,可以发现同一个数据段发生多次超时重传后,ssthresh 值并不会发生变化。
# packetdrill tcp_slow_start_1_021.pkt 0 10 21474836474 1 7#
# tcpdump -i any -nn port 8080tcpdump: data link type LINUX_SLL2tcpdump: verbose output suppressed, use -v[v]... for full protocol decodelistening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes22:16:53.927736 tun0 In IP 192.0.2.1.60031 > 192.168.110.180.8080: Flags [S], seq 0, win 10000, options [mss 1000,nop,nop,sackOK], length 022:16:53.927763 tun0 Out IP 192.168.110.180.8080 > 192.0.2.1.60031: Flags [S.], seq 44206648, ack 1, win 64240, options [mss 1460,nop,nop,sackOK], length 022:16:53.937987 tun0 In IP 192.0.2.1.60031 > 192.168.110.180.8080: Flags [.], ack 1, win 10000, length 022:16:53.958208 tun0 Out IP 192.168.110.180.8080 > 192.0.2.1.60031: Flags [P.], seq 1:1001, ack 1, win 64240, length 1000: HTTP22:16:54.171417 tun0 Out IP 192.168.110.180.8080 > 192.0.2.1.60031: Flags [P.], seq 1:1001, ack 1, win 64240, length 1000: HTTP22:16:54.387457 tun0 Out IP 192.168.110.180.8080 > 192.0.2.1.60031: Flags [P.], seq 1:1001, ack 1, win 64240, length 1000: HTTP22:16:54.823419 tun0 Out IP 192.168.110.180.8080 > 192.0.2.1.60031: Flags [P.], seq 1:1001, ack 1, win 64240, length 1000: HTTP22:16:55.026125 ? Out IP 192.168.110.180.8080 > 192.0.2.1.60031: Flags [F.], seq 1001, ack 1, win 64240, length 022:16:55.026148 ? In IP 192.0.2.1.60031 > 192.168.110.180.8080: Flags [R.], seq 1, ack 1, win 10000, length 0^C9 packets captured10 packets received by filter0 packets dropped by kernel#
继续思考另一个问题,如果是不同数据包发生了超时重传,ssthresh 值是否会变化呢。
基于此问题继续修改脚本,模拟发送第二个数据包,并发生多次 RTO 超时重传,如下,需注意超时重传和 ACK 确认的发生时间,选取打印多次值的时机。
# cat tcp_slow_start_1_022.pkt 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0+0 bind(3, ..., ...) = 0+0 listen(3, 1) = 0
+0 < S 0:0(0) win 10000 <mss 1000,nop,nop,sackOK>+0 > S. 0:0(0) ack 1 <...>+0.01 < . 1:1(0) ack 1 win 10000+0 accept(3, ..., ...) = 4
+0.01 %{print (tcpi_ca_state, tcpi_snd_cwnd, tcpi_snd_ssthresh)}%
+0.01 write(4,...,1000) = 1000+0 write(4,...,1000) = 1000
+0.8 %{print (tcpi_ca_state, tcpi_snd_cwnd, tcpi_snd_ssthresh)}%
+0 <. 1:1(0) ack 1001 win 10000+0 %{print (tcpi_ca_state, tcpi_snd_cwnd, tcpi_snd_ssthresh)}%
+1 %{print (tcpi_ca_state, tcpi_snd_cwnd, tcpi_snd_ssthresh)}%
+0 <. 1:1(0) ack 2001 win 10000+0 %{print (tcpi_ca_state, tcpi_snd_cwnd, tcpi_snd_ssthresh)}%#
运行脚本,并通过 tcpdump 捕获数据包如下,可以发现 TCP 拥塞控制状态、cwnd 和 ssthresh 值的多次变化。其中,Seq 1:1001 发生了两次超时重传,ssthresh 值为 7 ,而在 Seq 1001:2001 再次发生超时重传后,ssthresh 值由 7 变为了 2,说明了同一个数据包多次发生 RTO 超时和不同数据包发生 RTO 超时 ssthresh 值的变化情况还是有些许区别,前者不更新,后者更新。
# packetdrill tcp_slow_start_1_022.pkt 0 10 21474836474 1 74 2 74 1 20 2 2#
# tcpdump -i any -nn port 8080tcpdump: data link type LINUX_SLL2tcpdump: verbose output suppressed, use -v[v]... for full protocol decodelistening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes22:32:18.576241 tun0 In IP 192.0.2.1.58481 > 192.168.247.14.8080: Flags [S], seq 0, win 10000, options [mss 1000,nop,nop,sackOK], length 022:32:18.576345 tun0 Out IP 192.168.247.14.8080 > 192.0.2.1.58481: Flags [S.], seq 816399069, ack 1, win 64240, options [mss 1460,nop,nop,sackOK], length 022:32:18.586537 tun0 In IP 192.0.2.1.58481 > 192.168.247.14.8080: Flags [.], ack 1, win 10000, length 022:32:18.607860 tun0 Out IP 192.168.247.14.8080 > 192.0.2.1.58481: Flags [P.], seq 1:1001, ack 1, win 64240, length 1000: HTTP22:32:18.607912 tun0 Out IP 192.168.247.14.8080 > 192.0.2.1.58481: Flags [P.], seq 1001:2001, ack 1, win 64240, length 1000: HTTP22:32:18.635407 tun0 Out IP 192.168.247.14.8080 > 192.0.2.1.58481: Flags [P.], seq 1001:2001, ack 1, win 64240, length 1000: HTTP22:32:18.851457 tun0 Out IP 192.168.247.14.8080 > 192.0.2.1.58481: Flags [P.], seq 1:1001, ack 1, win 64240, length 1000: HTTP22:32:19.303485 tun0 Out IP 192.168.247.14.8080 > 192.0.2.1.58481: Flags [P.], seq 1:1001, ack 1, win 64240, length 1000: HTTP22:32:19.407953 tun0 In IP 192.0.2.1.58481 > 192.168.247.14.8080: Flags [.], ack 1001, win 10000, length 022:32:19.408002 tun0 Out IP 192.168.247.14.8080 > 192.0.2.1.58481: Flags [P.], seq 1001:2001, ack 1, win 64240, length 1000: HTTP22:32:20.263466 tun0 Out IP 192.168.247.14.8080 > 192.0.2.1.58481: Flags [P.], seq 1001:2001, ack 1, win 64240, length 1000: HTTP22:32:20.408090 ? In IP 192.0.2.1.58481 > 192.168.247.14.8080: Flags [.], ack 2001, win 10000, length 022:32:20.474926 ? Out IP 192.168.247.14.8080 > 192.0.2.1.58481: Flags [F.], seq 2001, ack 1, win 64240, length 022:32:20.474951 ? In IP 192.0.2.1.58481 > 192.168.247.14.8080: Flags [R.], seq 1, ack 2001, win 10000, length 0^C14 packets captured15 packets received by filter0 packets dropped by kernel#
注,Seq 1001:2001 初始发送后,第一次看到的重传为 TLP 。
往期推荐
1. Wireshark 提示和技巧 | 捕获点之 TCP 三次握手
2. Wireshark 提示和技巧 | a == ${a} 显示过滤宏
3. Wireshark TS | 防火墙空闲会话超时问题
4. Wireshark TS | 超时重传时间不翻倍
5. 网络设备 MTU MSS Jumboframe 全解
后台回复「TT」获取 Wireshark 提示和技巧系列 合集
后台回复「TS」获取 Wireshark Troubleshooting 系列 合集
如需交流或加技术群,可后台直接留言,我会在第一时间回复,谢谢!
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:Echo Reply 7ACE 7ACE《Wireshark & Packetdrill | TCP 慢启动(6)》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论