当前位置: 首页 > backend >正文

Wireshark TS | 发送数据超出接收窗口

前言

来自于测试中无意发现到的一个接收窗口满的案例,特殊,或者可以说我以前都没在实际场景中见过。一开始都没整太明白,花了些精力才算是弄清楚了些,记录分享下。

问题说明

在研究拥塞控制的慢启动阶段时,通过 packetdrill 写了一个脚本,如下。

# cat tcp_troubleshooting_1_001.pkt
`ethtool -K tun0 tso offethtool -K tun0 gso off`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 < P. 1:101(100) ack 1 win 2000
+0 > . 1:1(0) ack 101+0.01 write(4,...,4000) = 4000+0 `sleep 10`
#

TCP 三次握手中通告的 MSS 为 1000,也没有启用 WScale 因子,之后客户端发送了一个数据段,其中通告了自身的 Win 为 2000,服务器也正常响应了 ACK,之后服务器端写入了 4000 字节大小的数据包。

想象中的实验结果应该是,由于接收端 Win 2000 限制的缘故,服务器端只能发出 2 个 1000 字节的数据包,可惜想象很美好,结果却不一致,服务器发出了 4 个 1000 字节的数据包,无视接收窗口的限制,问题在哪?

# tcpdump -i any -nn port 8080
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
22:00:20.517879 tun0  In  IP 192.0.2.1.45917 > 192.168.235.30.8080: Flags [S], seq 0, win 10000, options [mss 1000,nop,nop,sackOK], length 0
22:00:20.517912 tun0  Out IP 192.168.235.30.8080 > 192.0.2.1.45917: Flags [S.], seq 2020381752, ack 1, win 64240, options [mss 1460,nop,nop,sackOK], length 0
22:00:20.528033 tun0  In  IP 192.0.2.1.45917 > 192.168.235.30.8080: Flags [.], ack 1, win 10000, length 0
22:00:20.538114 tun0  In  IP 192.0.2.1.45917 > 192.168.235.30.8080: Flags [P.], seq 1:101, ack 1, win 2000, length 100: HTTP
22:00:20.538139 tun0  Out IP 192.168.235.30.8080 > 192.0.2.1.45917: Flags [.], ack 101, win 64140, length 0
22:00:20.548217 tun0  Out IP 192.168.235.30.8080 > 192.0.2.1.45917: Flags [.], seq 1:1001, ack 101, win 64140, length 1000: HTTP
22:00:20.548221 tun0  Out IP 192.168.235.30.8080 > 192.0.2.1.45917: Flags [P.], seq 1001:2001, ack 101, win 64140, length 1000: HTTP
22:00:20.548226 tun0  Out IP 192.168.235.30.8080 > 192.0.2.1.45917: Flags [.], seq 2001:3001, ack 101, win 64140, length 1000: HTTP
22:00:20.548227 tun0  Out IP 192.168.235.30.8080 > 192.0.2.1.45917: Flags [P.], seq 3001:4001, ack 101, win 64140, length 1000: HTTP
#

问题分析

通常所说的发送窗口就是min(cwnd,rwnd),由于初始 CWND 为 10,所以初步的判断仍是 rwnd 的限制,但是为什么 Win 2000 没生效,确实让人费解。

第一步验证 rwnd ,考虑到 No.3 和 No4 均有 Win 值,如果 Win 2000 未生效的情况,是否是受限制于上一个 Win 10000。修改脚本如下,考虑测试简便,调整成了 3000 和 2000,仍然尝试写入 4000 字节大小的数据包。

# cat tcp_troubleshooting_1_002.pkt 
`ethtool -K tun0 tso offethtool -K tun0 gso off`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 3000
+0 accept(3, ..., ...) = 4+0.01 < P. 1:101(100) ack 1 win 2000
+0 > . 1:1(0) ack 101+0.01 write(4,...,4000) = 4000+0 `sleep 10`
#

可以看到服务器端发送的数据包只有 3 个,也就是受限于上一个 Win 3000,而不是最近的一个 Win 2000。

# tcpdump -i any -nn port 8080
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
22:30:05.357883 tun0  In  IP 192.0.2.1.40269 > 192.168.166.85.8080: Flags [S], seq 0, win 10000, options [mss 1000,nop,nop,sackOK], length 0
22:30:05.357911 tun0  Out IP 192.168.166.85.8080 > 192.0.2.1.40269: Flags [S.], seq 2967841851, ack 1, win 64240, options [mss 1460,nop,nop,sackOK], length 0
22:30:05.367982 tun0  In  IP 192.0.2.1.40269 > 192.168.166.85.8080: Flags [.], ack 1, win 3000, length 0
22:30:05.378045 tun0  In  IP 192.0.2.1.40269 > 192.168.166.85.8080: Flags [P.], seq 1:101, ack 1, win 2000, length 100: HTTP
22:30:05.378065 tun0  Out IP 192.168.166.85.8080 > 192.0.2.1.40269: Flags [.], ack 101, win 64140, length 0
22:30:05.388145 tun0  Out IP 192.168.166.85.8080 > 192.0.2.1.40269: Flags [.], seq 1:1001, ack 101, win 64140, length 1000: HTTP
22:30:05.388155 tun0  Out IP 192.168.166.85.8080 > 192.0.2.1.40269: Flags [P.], seq 1001:2001, ack 101, win 64140, length 1000: HTTP
22:30:05.388158 tun0  Out IP 192.168.166.85.8080 > 192.0.2.1.40269: Flags [.], seq 2001:3001, ack 101, win 64140, length 1000: HTTP
#

那么为什么服务器端在收到 No.4 也就是 Win 2000 的数据包后,没有更新 rwnd ?带着疑惑,翻了源码和很多资料,最后才找到答案,相关发送窗口更新的处理函数为 tcp_ack_update_window -> tcp_may_update_window ,如下。

/* Check that window update is acceptable.* The function assumes that snd_una<=ack<=snd_next.*/
static inline bool tcp_may_update_window(const struct tcp_sock *tp,const u32 ack, const u32 ack_seq,const u32 nwin)
{return	after(ack, tp->snd_una) ||after(ack_seq, tp->snd_wl1) ||(ack_seq == tp->snd_wl1 && nwin > tp->snd_wnd);
}

主要有三个判断条件,after(ack, tp->snd_una) || after(ack_seq, tp->snd_wl1) || (ack_seq == tp->snd_wl1 && nwin > tp->snd_wnd) 。

  1. after(ack, tp->snd_una),由于 No.4 的 ACK 并没有确认新数据,所以判断不成立;
  2. after(ack_seq, tp->snd_wl1),由于 No.4 的 Seq Num 和之前 snd_wl1 一样,所以判断也不成立;
  3. (ack_seq == tp->snd_wl1 && nwin > tp->snd_wnd) ,由于 No.4 的 Seq Num 和之前 snd_wl1 一样,前一个成立,但是 nwin 为 2000 并不大于 snd_wnd 10000,后一个不成立,所以判断也不成立。

综合 return 为 0 ,也就是说并不会更新发送窗口为 2000,仍保持发送窗口为 10000,因此服务器最终是可以发出 4 个 1000 字节的数据包。

答案是找到了,但既然到这一步了,就继续通过实验验证,巩固下这个知识点。

实验扩展

通过三个实验验证以上三个判断条件,在各自条件成立的情况下,从而更新发送窗口。

  1. after(ack, tp->snd_una)

修改脚本,使得 ACK 确认新数据,如下,ACK win 2000 的 ack num 1001 确认了新数据。

# cat tcp_troubleshooting_1_003.pkt 
`ethtool -K tun0 tso offethtool -K tun0 gso off`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 < P. 1:101(100) ack 1 win 3000
+0 > . 1:1(0) ack 101+0.01 write(4,...,1000) = 1000
+0.01 < . 101:101(0) ack 1001 win 2000+0.01 write(4,...,4000) = 4000
#

Win 2000 限制生效,服务器端仅能发出 2 个 MSS 1000 大小的数据包。

# tcpdump -i any -nn port 8080
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
22:55:16.017895 ?     In  IP 192.0.2.1.36917 > 192.168.24.159.8080: Flags [S], seq 0, win 10000, options [mss 1000,nop,nop,sackOK], length 0
22:55:16.017930 ?     Out IP 192.168.24.159.8080 > 192.0.2.1.36917: Flags [S.], seq 4123551004, ack 1, win 64240, options [mss 1460,nop,nop,sackOK], length 0
22:55:16.028014 ?     In  IP 192.0.2.1.36917 > 192.168.24.159.8080: Flags [.], ack 1, win 10000, length 0
22:55:16.038118 ?     In  IP 192.0.2.1.36917 > 192.168.24.159.8080: Flags [P.], seq 1:101, ack 1, win 3000, length 100: HTTP
22:55:16.038154 ?     Out IP 192.168.24.159.8080 > 192.0.2.1.36917: Flags [.], ack 101, win 64140, length 0
22:55:16.048290 ?     Out IP 192.168.24.159.8080 > 192.0.2.1.36917: Flags [P.], seq 1:1001, ack 101, win 64140, length 1000: HTTP
22:55:16.058342 ?     In  IP 192.0.2.1.36917 > 192.168.24.159.8080: Flags [.], ack 1001, win 2000, length 0
22:55:16.068472 ?     Out IP 192.168.24.159.8080 > 192.0.2.1.36917: Flags [.], seq 1001:2001, ack 101, win 64140, length 1000: HTTP
22:55:16.068479 ?     Out IP 192.168.24.159.8080 > 192.0.2.1.36917: Flags [P.], seq 2001:3001, ack 101, win 64140, length 1000: HTTP
#
  1. after(ack_seq, tp->snd_wl1)

修改脚本,使得 ACK Seq num 101 更新,大于之前的 snd_wl1 1 。

# cat tcp_troubleshooting_1_004.pkt 
`ethtool -K tun0 tso offethtool -K tun0 gso off`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 < P. 1:101(100) ack 1 win 3000
+0 > . 1:1(0) ack 101+0.01 < P. 101:201(100) ack 1 win 2000
+0 > . 1:1(0) ack 201+0.01 write(4,...,4000) = 4000
#

Win 2000 限制生效,服务器端仅能发出 2 个 MSS 1000 大小的数据包。

# tcpdump -i any -nn port 8080
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
23:00:54.817885 tun0  In  IP 192.0.2.1.49051 > 192.168.214.190.8080: Flags [S], seq 0, win 10000, options [mss 1000,nop,nop,sackOK], length 0
23:00:54.817913 tun0  Out IP 192.168.214.190.8080 > 192.0.2.1.49051: Flags [S.], seq 2399892350, ack 1, win 64240, options [mss 1460,nop,nop,sackOK], length 0
23:00:54.827992 ?     In  IP 192.0.2.1.49051 > 192.168.214.190.8080: Flags [.], ack 1, win 10000, length 0
23:00:54.838065 ?     In  IP 192.0.2.1.49051 > 192.168.214.190.8080: Flags [P.], seq 1:101, ack 1, win 3000, length 100: HTTP
23:00:54.838084 ?     Out IP 192.168.214.190.8080 > 192.0.2.1.49051: Flags [.], ack 101, win 64140, length 0
23:00:54.848151 ?     In  IP 192.0.2.1.49051 > 192.168.214.190.8080: Flags [P.], seq 101:201, ack 1, win 2000, length 100: HTTP
23:00:54.848170 ?     Out IP 192.168.214.190.8080 > 192.0.2.1.49051: Flags [.], ack 201, win 64040, length 0
23:00:54.858248 ?     Out IP 192.168.214.190.8080 > 192.0.2.1.49051: Flags [.], seq 1:1001, ack 201, win 64040, length 1000: HTTP
23:00:54.858252 ?     Out IP 192.168.214.190.8080 > 192.0.2.1.49051: Flags [P.], seq 1001:2001, ack 201, win 64040, length 1000: HTTP
#
  1. (ack_seq == tp->snd_wl1 && nwin > tp->snd_wnd)

修改脚本,使得 nwin 3000 大于 snd_wnd 2000。

# cat tcp_troubleshooting_1_005.pkt 
`ethtool -K tun0 tso offethtool -K tun0 gso off`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 2000
+0 accept(3, ..., ...) = 4+0.01 < P. 1:101(100) ack 1 win 3000
+0 > . 1:1(0) ack 101+0.01 write(4,...,4000) = 4000
#

Win 3000 限制生效,服务器端能发出 3 个 MSS 1000 大小的数据包。

# tcpdump -i any -nn port 8080
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
23:03:00.017903 ?     In  IP 192.0.2.1.34617 > 192.168.35.60.8080: Flags [S], seq 0, win 10000, options [mss 1000,nop,nop,sackOK], length 0
23:03:00.017940 ?     Out IP 192.168.35.60.8080 > 192.0.2.1.34617: Flags [S.], seq 3161188315, ack 1, win 64240, options [mss 1460,nop,nop,sackOK], length 0
23:03:00.028034 ?     In  IP 192.0.2.1.34617 > 192.168.35.60.8080: Flags [.], ack 1, win 2000, length 0
23:03:00.038103 ?     In  IP 192.0.2.1.34617 > 192.168.35.60.8080: Flags [P.], seq 1:101, ack 1, win 3000, length 100: HTTP
23:03:00.038126 ?     Out IP 192.168.35.60.8080 > 192.0.2.1.34617: Flags [.], ack 101, win 64140, length 0
23:03:00.048222 ?     Out IP 192.168.35.60.8080 > 192.0.2.1.34617: Flags [.], seq 1:1001, ack 101, win 64140, length 1000: HTTP
23:03:00.048232 ?     Out IP 192.168.35.60.8080 > 192.0.2.1.34617: Flags [P.], seq 1001:2001, ack 101, win 64140, length 1000: HTTP
23:03:00.048235 ?     Out IP 192.168.35.60.8080 > 192.0.2.1.34617: Flags [.], seq 2001:3001, ack 101, win 64140, length 1000: HTTP
#

问题总结

实际中的场景,你们是否有见过,感觉奇奇怪怪的知识又增加了。

http://www.xdnf.cn/news/16387.html

相关文章:

  • Frontiers in Psychology投稿LaTeX(三)
  • 元宇宙中的“虫洞“:技术实现、应用场景与未来挑战
  • J3160迷你小主机 性能测试 对比i3-4170 以及服务器
  • Python Pandas.qcut函数解析与实战教程
  • RS485转profinet网关如何让JRT激光测距传感器开启自动模式连续测量模式
  • 数据结构基础内容(第九篇:最短路径)
  • DP之背包基础
  • AutoLabelImg:高效的数据自动化标注工具和下载
  • Gradio.NET 中文快速入门与用法说明
  • 2025年7月25日-7月26日 · AI 今日头条
  • 在Luckfox Lyra(Zero W)上将TF卡格式化为ext4文件系统
  • 《 集成异步任务与定时调度:线程池与任务中心设计》
  • AI与区块链Web3技术融合:重塑数字经济的未来格局
  • 2025年项目数据看板工具选型指南,精选12款
  • SQL中的group by和having区别详解
  • 【C语言网络编程】HTTP 客户端请求(基于 Socket 的完整实现)
  • 神经网络知识讨论
  • 网易大模型算法岗面经80道
  • 【学习笔记】MimicGen: 基于人类演示的可扩展机器人学习数据生成系统
  • 批量重命名带编号工具,附免费地址
  • idea打开后project窗口未显示项目名称的解决方案
  • k8s的权限
  • tlias智能学习辅助系统--Filter(过滤器)
  • Ansible列出常见操作系统的发行版,Ansible中使用facts变量的两种方式
  • CH341 Linux驱动 没有 /dev/ttyCH341USB0
  • Linux文件系统管理——NFS服务端的安装配置与NFS客户端的安装与挂载实操教程
  • 【AI】联网模式
  • Scrapy分布式爬虫数据统计全栈方案:构建企业级监控分析系统
  • GPU运维常见问题处理
  • 【C++】stack和queue的模拟实现