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

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

前言

之前写了一篇关于《发送数据超出接收窗口》的文章,描述的是测试中无意发现的一个接收窗口满的特殊案例,表面现象是服务器端无视对端接收窗口的限制,发出了超出接收窗口的数据,当然最后还是找到了原因。

上次讲的是发送端能发出超出接收窗口的数据,这次讲讲接收端是否能接收的问题。

问题描述

首先简单回顾一下上次发送端能发的问题,如下脚本以及数据包信息,相关原因解释详见上篇文章。

# 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`
#
# 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
#

本次研究了下接收端的问题,如下脚本,通过 setsockopt 方式限制了服务器端的接收缓存。

# cat tcp_troubleshooting_1_006.pkt 
0   socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_RCVBUF, [3000],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+0.01 < P. 1:1461(1460) ack 1 win 10000+0.02 < P. 1461:2921(1460) ack 1 win 10000
+0 < P. 2921:4381(1460) ack 1 win 10000+0.02 read (4,...,4380) = 4380+0 `sleep 1`
#

通过 tcpdump 抓包信息如下,可以看到服务器的接收窗口大小为 2920,客户端发送了第一个 1460 字节大小的数据,服务器端进行了 ACK 响应,由于应用未读取数据的缘故,接收窗口大小降为了 1460 大小,之后客户端继续发送了两个 1460 字节大小的数据,理论上由于接收窗口满的限制,服务器端仅会正常接收第二个数据段,而丢弃第三个数据段,但此时看到的现象是,服务器端响应的 ACK 数据包 ACK Num 为 4381 ,也就是说实际 TCP 层面是确认了第二个和第三个数据段,而且通过 read() 也证明可以读取 4380 长度大小的数据。

这都说明了接收端是能接收超出自身接收窗口的数据,并正常读取,为什么?

# 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
19:31:58.237559 tun0  In  IP 192.0.2.1.37193 > 192.168.25.166.8080: Flags [S], seq 0, win 10000, options [mss 1460], length 0
19:31:58.237584 tun0  Out IP 192.168.25.166.8080 > 192.0.2.1.37193: Flags [S.], seq 346115781, ack 1, win 2920, options [mss 1460], length 0
19:31:58.247660 tun0  In  IP 192.0.2.1.37193 > 192.168.25.166.8080: Flags [.], ack 1, win 10000, length 0
19:31:58.257728 tun0  In  IP 192.0.2.1.37193 > 192.168.25.166.8080: Flags [P.], seq 1:1461, ack 1, win 10000, length 1460: HTTP
19:31:58.257746 tun0  Out IP 192.168.25.166.8080 > 192.0.2.1.37193: Flags [.], ack 1461, win 1460, length 0
19:31:58.277741 tun0  In  IP 192.0.2.1.37193 > 192.168.25.166.8080: Flags [P.], seq 1461:2921, ack 1, win 10000, length 1460: HTTP
19:31:58.277764 tun0  In  IP 192.0.2.1.37193 > 192.168.25.166.8080: Flags [P.], seq 2921:4381, ack 1, win 10000, length 1460: HTTP
19:31:58.297823 tun0  Out IP 192.168.25.166.8080 > 192.0.2.1.37193: Flags [.], ack 4381, win 2920, length 0
19:31:59.299615 ?     Out IP 192.168.25.166.8080 > 192.0.2.1.37193: Flags [F.], seq 1, ack 4381, win 2920, length 0
19:31:59.299641 ?     In  IP 192.0.2.1.37193 > 192.168.25.166.8080: Flags [R.], seq 4381, ack 1, win 10000, length 0
#

问题分析

首先说结论,这个现象是因为在接收第三个数据段时进入了快速路径下的数据包处理流程,在快速路径下减少了很多处理,也就是并不会执行很严格的检查,包括接收窗口大小是否为 0 。

相关的内核代码在 tcp_rcv_established() 中,这段代码主要是 Linux 内核中 TCP 快速路径(Fast Path)处理逻辑的一部分,用于判断一个接收到的 TCP 数据包是否可以快速处理,如下。

void tcp_rcv_established(struct sock *sk, struct sk_buff *skb)
{const struct tcphdr *th = (const struct tcphdr *)skb->data;struct tcp_sock *tp = tcp_sk(sk);unsigned int len = skb->len;...if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags &&TCP_SKB_CB(skb)->seq == tp->rcv_nxt &&!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) {...if (len <= tcp_header_len) {...} else {...if ((int)skb->truesize > sk->sk_forward_alloc)goto step5;...}}
...step5:if (tcp_ack(sk, skb, FLAG_SLOWPATH | FLAG_UPDATE_TS_RECENT) < 0)goto discard;tcp_rcv_rtt_measure_ts(sk, skb);/* Process urgent data. */tcp_urg(sk, skb, th);/* step 7: process the segment text */tcp_data_queue(sk, skb);tcp_data_snd_check(sk);tcp_ack_snd_check(sk);return;...
}

主要分为两个部分,首先是第一个 if 语句判断,简要说明如下:

	if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags &&TCP_SKB_CB(skb)->seq == tp->rcv_nxt &&!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt))tcp_flag_word(th):获取 TCP 报文头中的标志位(包括 TCP 首部长度、ACK 标志和窗口大小等)。
TCP_HP_BITS:一个掩码,用于提取 TCP 报文头中与 pred_flags 相关的字段(通常是 TCP 首部长度、ACK 标志和窗口大小)。
tp->pred_flags:struct tcp_sock 中的字段,存储了预期的 TCP 首部信息(如首部长度、ACK 标志和窗口大小)。作用:检查接收到的 TCP 报文头中的关键字段是否与 pred_flags 中存储的预期值一致。如果一致,说明该报文符合快速路径的预期。

因为在 packetdrill 实验脚本中,以下服务器端所接收的数据段中,包括 TCP 首部长度、ACK 标志和窗口大小均无变化,所以 tp->pred_flags 值也一直没变化,因此预期都会进入快速路径的数据包处理流程,再进行后续判断。

# cat tcp_troubleshooting_1_006.pkt 
...
+0.01 < . 1:1(0) ack 1 win 10000+0.01 < P. 1:1461(1460) ack 1 win 10000+0.02 < P. 1461:2921(1460) ack 1 win 10000+0 < P. 2921:4381(1460) ack 1 win 10000

第二个部分,是对于当前 socket ,有一个预分配的缓存额度,也就是可以分配的额外内存,当接收的数据没有超过这个预分配的缓存,则继续快速处理,能够正常接收,否则会跳转到慢速路径处理。

		if (len <= tcp_header_len) {...} else {...if ((int)skb->truesize > sk->sk_forward_alloc)goto step5;...}

而对于当前 packetdrill 的实验脚本中的三个数据段,实际处理流程分别是:

数据段skb->truesizesk->sk_forward_alloc处理路径接收窗口接收数据
1:1461(1460)23040慢速处理2920
1461:2921(1460)23041792慢速处理1460
2921:4381(1460)23043584快速处理0

其中 sk->sk_forward_alloc 是一个动态调整的值,用于管理套接字的内存分配。它通过预分配内存额度来优化内存分配的效率,并根据当前的内存使用情况和协议的内存限制动态调整其值,这种机制有助于减少内存分配的开销,提高网络协议栈的性能。

当然进入了快速处理,不代表之后一直会是快速处理流程,同样又会是上述一顿判断。可以修改脚本,继续注入一个数据进行观察。

# cat tcp_troubleshooting_1_007.pkt 
0   socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_RCVBUF, [3000],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+0.01 < P. 1:1461(1460) ack 1 win 10000+0.02 < P. 1461:2921(1460) ack 1 win 10000
+0 < P. 2921:4381(1460) ack 1 win 10000
+0 < P. 4381:5841(1460) ack 1 win 10000+0 `sleep 1`
#

可以看到倒数第三个数据包,也就是服务器端的 ACK 数据包 ACK Num 仍为 4381 ,也就是只是接收了第二个和第三个数据段,而丢弃了第四个数据段。

# 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:02:55.169563 tun0  In  IP 192.0.2.1.54727 > 192.168.187.220.8080: Flags [S], seq 0, win 10000, options [mss 1460], length 0
22:02:55.169590 tun0  Out IP 192.168.187.220.8080 > 192.0.2.1.54727: Flags [S.], seq 3345930605, ack 1, win 2920, options [mss 1460], length 0
22:02:55.179696 tun0  In  IP 192.0.2.1.54727 > 192.168.187.220.8080: Flags [.], ack 1, win 10000, length 0
22:02:55.189809 tun0  In  IP 192.0.2.1.54727 > 192.168.187.220.8080: Flags [P.], seq 1:1461, ack 1, win 10000, length 1460: HTTP
22:02:55.189834 tun0  Out IP 192.168.187.220.8080 > 192.0.2.1.54727: Flags [.], ack 1461, win 1460, length 0
22:02:55.209814 tun0  In  IP 192.0.2.1.54727 > 192.168.187.220.8080: Flags [P.], seq 1461:2921, ack 1, win 10000, length 1460: HTTP
22:02:55.209855 tun0  In  IP 192.0.2.1.54727 > 192.168.187.220.8080: Flags [P.], seq 2921:4381, ack 1, win 10000, length 1460: HTTP
22:02:55.209866 tun0  In  IP 192.0.2.1.54727 > 192.168.187.220.8080: Flags [P.], seq 4381:5841, ack 1, win 10000, length 1460: HTTP
22:02:55.253273 tun0  Out IP 192.168.187.220.8080 > 192.0.2.1.54727: Flags [.], ack 4381, win 0, length 0
22:02:56.211782 tun0  Out IP 192.168.187.220.8080 > 192.0.2.1.54727: Flags [R.], seq 1, ack 4381, win 2920, length 0
22:02:56.211810 tun0  In  IP 192.0.2.1.54727 > 192.168.187.220.8080: Flags [R.], seq 5841, ack 1, win 10000, length 0
#

而对于当前 packetdrill 的实验脚本中的四个数据段,实际处理流程分别是:

数据段skb->truesizesk->sk_forward_alloc处理路径接收窗口接收数据
1:1461(1460)23040慢速处理2920
1461:2921(1460)23041792慢速处理1460
2921:4381(1460)23043584快速处理0
4381:5841(1460)23041280慢速处理0

问题总结

弄明白一个问题真是不容易,感谢网上分享各类相关资料的专家,感谢 chatgpt/kimi 。🙏


文章转载自:

http://ZwLp2u4P.fnfxp.cn
http://1TyaKuJv.fnfxp.cn
http://YwCa8ryZ.fnfxp.cn
http://eTLyKxlo.fnfxp.cn
http://XLtxixaK.fnfxp.cn
http://c6cBxYdl.fnfxp.cn
http://NXGgncxr.fnfxp.cn
http://WTree299.fnfxp.cn
http://aBzNhhWA.fnfxp.cn
http://UyKE7eNU.fnfxp.cn
http://PbVAuzaz.fnfxp.cn
http://jCzLsr35.fnfxp.cn
http://CIgoWh0e.fnfxp.cn
http://Ox4xygRh.fnfxp.cn
http://gzrlrOnC.fnfxp.cn
http://3pL66Xrg.fnfxp.cn
http://PBuUw9d9.fnfxp.cn
http://m2WUyJJI.fnfxp.cn
http://0bHi3bh4.fnfxp.cn
http://BNUcOJiF.fnfxp.cn
http://MBnQyYAK.fnfxp.cn
http://Wzq4VOWq.fnfxp.cn
http://US1ukhHn.fnfxp.cn
http://PSPZE7I7.fnfxp.cn
http://xsSEOlTy.fnfxp.cn
http://S4C6qPVW.fnfxp.cn
http://Ns8Smohn.fnfxp.cn
http://Qaewp9sh.fnfxp.cn
http://GM7YlXVo.fnfxp.cn
http://CBXtzKzO.fnfxp.cn
http://www.dtcms.com/a/375501.html

相关文章:

  • 第一代:嵌入式本地状态(Flink 1.x)
  • 4.1-中间件之Redis
  • Django ModelForm:快速构建数据库表单
  • 【迭代】:本地高性能c++对话系统e2e_voice
  • SSE与Websocket、Http的关系
  • 蓓韵安禧DHA展现温和配方的藻油与鱼油营养特色
  • 基于UNet的视网膜血管分割系统
  • python函数和面向对象
  • 嵌入式 - ARM(3)从基础调用到 C / 汇编互调
  • 07MySQL存储引擎与索引优化
  • 面向OS bug的TypeState分析
  • 【文献笔记】Task allocation for multi-AUV system: A review
  • 小红书批量作图软件推荐运营大管家小红书批量作图工具
  • ArrayList详解与实际应用
  • 德意志飞机公司与DLR合作完成D328 UpLift演示机地面振动测试
  • MongoDB 备份与恢复终极指南:mongodump 和 mongorestore 深度实战
  • ctfshow - web - 命令执行漏洞总结(二)
  • 基于STM32的GPS北斗定位系统
  • 2025年大陆12寸晶圆厂一览
  • VMware Workstation Pro 安装教程
  • Java Spring @Retention三种保留策略
  • 低代码平台的核心组件与功能解析:红迅低代码平台实战探秘
  • linux sudo权限
  • PM2 管理后端(设置项目自启动)
  • 中国香港服务器中常提到的双向/全程CN2是什么意思?
  • DCS+PLC协同优化:基于MQTT的分布式控制系统能效提升案例
  • Backend
  • 分布式专题——6 Redis缓存设计与性能优化
  • 《智能网联汽车交通仿真软件可信度评估》团标启动会圆满举办
  • 无人机云台电压类型及测量方法