Wireshark&Packetdrill|TCP慢启动(6)

admin 2026-02-10 14:56:36 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文通过packetdrill脚本实验深入分析TCP慢启动机制,重点研究RTO超时重传场景下拥塞窗口(cwnd)和慢启动阈值(ssthresh)的变化规律。实验发现:首次RTO超时后cwnd重置为1,ssthresh减半;同一数据包多次RTO超时不会进一步降低ssthresh;但不同数据包相继发生RTO超时会导致ssthresh持续减半。文章详细展示了TCP_CA_Loss状态转换过程,为理解Linux内核TCP拥塞控制状态机提供了实测依据。 综合评分: 78 文章分类: 网络协议,漏洞分析,安全工具,安全运营,其他


cover_image

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&nbsp;0&nbsp; &nbsp;socket(..., SOCK_STREAM, IPPROTO_TCP) =&nbsp;3+0&nbsp;&nbsp;setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1],&nbsp;4) =&nbsp;0+0&nbsp;&nbsp;bind(3, ..., ...) =&nbsp;0+0&nbsp;&nbsp;listen(3,&nbsp;1) =&nbsp;0
+0&nbsp;< S&nbsp;0:0(0) win&nbsp;10000&nbsp;<mss&nbsp;1000,nop,nop,sackOK>+0&nbsp;> S.&nbsp;0:0(0) ack&nbsp;1&nbsp;<...>+0.01&nbsp;< .&nbsp;1:1(0) ack&nbsp;1&nbsp;win&nbsp;10000+0&nbsp;accept(3, ..., ...) =&nbsp;4
+0.01&nbsp;%{print&nbsp;(tcpi_ca_state, tcpi_snd_cwnd, tcpi_snd_ssthresh)}%#

# packetdrill tcp_slow_start_1_019.pkt&nbsp;0&nbsp;10&nbsp;2147483647#

修改脚本,增加发送数据且发生第一次 RTO 超时重传的情形,如下。

# cat tcp_slow_start_1_020.pkt&nbsp;0 &nbsp; socket(..., SOCK_STREAM, IPPROTO_TCP) = 3+0 &nbsp;setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0+0 &nbsp;bind(3, ..., ...) = 0+0 &nbsp;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&nbsp;0&nbsp;10&nbsp;21474836474&nbsp;1&nbsp;7#

# tcpdump -i any -nn port 8080tcpdump: data link type LINUX_SLL2tcpdump: verbose output suppressed, use -v[v]... for full protocol decodelistening&nbsp;on&nbsp;any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length&nbsp;262144&nbsp;bytes22:02:21.247965&nbsp;tun0 &nbsp;In &nbsp;IP&nbsp;192.0.2.1.56619&nbsp;>&nbsp;192.168.140.79.8080: Flags&nbsp;[S], seq 0, win 10000, options [mss 1000,nop,nop,sackOK], length 022:02:21.247986 tun0 &nbsp;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 &nbsp;In &nbsp;IP 192.0.2.1.56619 > 192.168.140.79.8080: Flags [.], ack 1, win 10000, length 022:02:21.278238 tun0 &nbsp;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 &nbsp;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 &nbsp;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 ? &nbsp; &nbsp; 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 ? &nbsp; &nbsp; In &nbsp;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&nbsp;0 &nbsp; socket(..., SOCK_STREAM, IPPROTO_TCP) = 3+0 &nbsp;setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0+0 &nbsp;bind(3, ..., ...) = 0+0 &nbsp;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&nbsp;0&nbsp;10&nbsp;21474836474&nbsp;1&nbsp;7#

# tcpdump -i any -nn port 8080tcpdump: data link type LINUX_SLL2tcpdump: verbose output suppressed, use -v[v]... for full protocol decodelistening&nbsp;on&nbsp;any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length&nbsp;262144&nbsp;bytes22:16:53.927736&nbsp;tun0 &nbsp;In &nbsp;IP&nbsp;192.0.2.1.60031&nbsp;>&nbsp;192.168.110.180.8080: Flags&nbsp;[S], seq 0, win 10000, options [mss 1000,nop,nop,sackOK], length 022:16:53.927763 tun0 &nbsp;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 &nbsp;In &nbsp;IP 192.0.2.1.60031 > 192.168.110.180.8080: Flags [.], ack 1, win 10000, length 022:16:53.958208 tun0 &nbsp;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 &nbsp;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 &nbsp;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 &nbsp;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 ? &nbsp; &nbsp; 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 ? &nbsp; &nbsp; In &nbsp;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&nbsp;0 &nbsp; socket(..., SOCK_STREAM, IPPROTO_TCP) = 3+0 &nbsp;setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0+0 &nbsp;bind(3, ..., ...) = 0+0 &nbsp;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&nbsp;0&nbsp;10&nbsp;21474836474&nbsp;1&nbsp;74&nbsp;2&nbsp;74&nbsp;1&nbsp;20&nbsp;2&nbsp;2#

# tcpdump -i any -nn port 8080tcpdump: data link type LINUX_SLL2tcpdump: verbose output suppressed, use -v[v]... for full protocol decodelistening&nbsp;on&nbsp;any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length&nbsp;262144&nbsp;bytes22:32:18.576241&nbsp;tun0 &nbsp;In &nbsp;IP&nbsp;192.0.2.1.58481&nbsp;>&nbsp;192.168.247.14.8080: Flags&nbsp;[S], seq 0, win 10000, options [mss 1000,nop,nop,sackOK], length 022:32:18.576345 tun0 &nbsp;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 &nbsp;In &nbsp;IP 192.0.2.1.58481 > 192.168.247.14.8080: Flags [.], ack 1, win 10000, length 022:32:18.607860 tun0 &nbsp;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 &nbsp;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 &nbsp;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 &nbsp;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 &nbsp;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 &nbsp;In &nbsp;IP 192.0.2.1.58481 > 192.168.247.14.8080: Flags [.], ack 1001, win 10000, length 022:32:19.408002 tun0 &nbsp;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 &nbsp;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 ? &nbsp; &nbsp; In &nbsp;IP 192.0.2.1.58481 > 192.168.247.14.8080: Flags [.], ack 2001, win 10000, length 022:32:20.474926 ? &nbsp; &nbsp; 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 ? &nbsp; &nbsp; In &nbsp;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)》

评论:0   参与:  2