TCP 传输时 sk_buff 的 clone 和 unclone

周一有位朋友咨询个问题,问题本身不重要,但牵扯出的细节却是非常有趣。

Linux 内核协议栈的 skb 设计非常高效和精巧,多个 skb 可以指向同一块 data,这就是 clone,当 data 不止一个 skb 指示时,任何一个 skb 要修改 data 时,必须 unclone,其内部实现就是一个简单的 cow(copy on write),这就是 unclone,原理如下:
在这里插入图片描述

clone 和 unclone 的含义简述如下:

  • clone,复制一个新 skb,指向相同 data,dataref 递增;
  • unclone,复制一份新 data,原 dataref 递减,skb 指向新 data;

值得注意的是 skb 的 clone 和 share 的区别:

  • clone,针对 skb 的 data;
  • share,针对 skb 结构体本身;

因此,kfree_skb 就非常清晰了:

  • 先递减 share 计数,自己是最后一个才继续释放 skb 的其它内容;
  • 再递减 data 的 dataref,自己是最后一个才彻底释放 data 本身;

至于 skb_copy,pskb_copy,自然就不必说,理解 skb_clone,skb_unclone,kfree_skb 就够了。
下面是 TCP 传输和重传过程的 clone,unclone 序列:
在这里插入图片描述

TCP 传输和重传时,红黑树(解释理由)上的本体 skb 结构体始终未变,变的是 clone skb 结构体和 data:

  • 每次传输或重传时,均会 clone 一份本体 skb 实际传输,clone 过程本体 skb 的 data 不变,dataref 递增;
  • 每次重传时,如果 dataref 大于 1,本体 skb 会 unclone,复制一份 data ,原 data 的 dataref 递减,回到 1;

unclone 后留下的原 data 并未游离,因为还有上下文引用它们,可能驱动尚未发送完毕,等发完了一般会有中断通知,那就留给 clone 它的上下文去 kfree 了。

以下是上面知识在处理朋友问题时的一个应用,该应用过程反过来也能加深上面知识的理解和内化。

问题是这样的。理论上 TCP 重传时要从重传队列 head 开始,可抓包却发现先重传了最后 fin,再次重传时才重传队列更前面的 data。

初步猜测是 tlp,过年期间写过一个关于 tlp 的,详见 探秘 TCP TLP:从背景到实现。为帮朋友定位问题,我写了下面的 pdrill 复现脚本,并打开 tlp:

   // 开启 tlp//sysctl -q net.ipv4.tcp_early_retrans=4 net.ipv4.tcp_recovery=10 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 32792 <mss 1460,sackOK, nop, nop, nop,wscale 7>+0 > S. 0:0(0) ack 1 <mss 1460,nop, nop, sackOK, nop, wscale 7>+0 < . 1:1(0) ack 1 win 257+0 accept(3, ..., ...) = 4+0 write(4, ..., 1360) = 1360+0 close(4) = 0+10 < . 1:1(0) ack 2821 win 257

通篇没收到任何 ack, 1360 字节数据和 close 的 fin 均要被重传,会先重传最后的 fin,但理论上后续重传 1360 字节时会将 fin 合并带上,几乎可以完全复现场景:
在这里插入图片描述

然而抓包却是这样:
在这里插入图片描述

并没有合并捎带 fin。

不合并就不合并了,不是什么大事,本身 tcp collapse 触发条件就不止一个。但作为 pdrill 本地测试,最基本的场景,立场应该是想让它 collapse 它就得 collapse,一定是哪里出了问题才导致 1360 字节的报文没有 collapse fin。

tcp 重传 collapse 的条件是没有其它上下文引用该 skb 的 data,即 shinfo->dataref == 1,以下是详细观测 skb shinfo->dataref 的脚本:

bpftrace -e '
//kprobe:__tcp_transmit_skb  // 尚未 clone,dataref = 1
//kprobe:__ip_queue_xmit     // 已经 clone,dataref = 2
//kprobe:__dev_queue_xmit
//kprobe:dev_hard_start_xmit
//kprobe:dev_queue_xmit_nit // PACKET 套接字等抓包程序会再次 clone,dataref = 3,处理完成后 dataref = 2
//kprobe:网卡 xmit 回调      // 传输完毕由驱动负载 kfree_skb,dataref = 1
{$skb = (struct sk_buff *)arg0;$dev = (struct net_device *)($skb->dev);if (strncmp($dev->name, "tun0", 4) == 0) {$shinfo = (struct skb_shared_info *)($skb->head + $skb->end);$dataref = $shinfo->dataref.counter;printf("tun0 skb=%p dataref=%d\n", $skb, $dataref & 0xffff);}
}
'

结果是到了 tun_net_xmit 时,dataref = 3,但在 dev_hard_start_xmit 时 dataref 还是 2,这中间只有 PACKET 套接字,类似抓包路径了。而抓包需要 copy data,肯定是需要 clone skb 的。还果然是这个原因:

root@vbox:~# strace -e trace=socket packetdrill ./tlp-with-fin.pkt
...
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=6624, si_uid=0, si_status=0, si_utime=0, si_stime=0} ---
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=6626, si_uid=0, si_status=0, si_utime=0, si_stime=0} ---
socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) = 6

也可在 packetdrill 运行期间通过 procfs 确认:

root@vbox:~# cat /proc/net/packet
sk               RefCnt Type Proto  Iface R Rmem   User   Inode
00000000dafae456 3      2    0003   0     1 0      0      42872

但即使减少了 PACKET 套接字的 1 个 ref,还有 1 个 ref 直到 tun_net_xmit 也没释放,原因在于 packetdrill 并未 read tun0fd。

为了避免这种本地测试工具不处理 read 造成的额外混乱,我觉得 tun 驱动应该增加一个 “低效率” 模式,即 skb 进入 tun 驱动的 tun_net_xmit 回调时直接转交掉,多一次复制,少一份混乱:

static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
{struct tun_struct *tun = netdev_priv(dev);enum skb_drop_reason drop_reason;int txq = skb->queue_mapping;struct netdev_queue *queue;struct tun_file *tfile;int len = skb->len;struct sk_buff *nskb = skb;rcu_read_lock();if (tun-> & 低效) {skb = skb_copy(nskb, GFP_ATOMIC);kfree_skb(nskb);// 这里顺带帮 PACKET 套接字解除一个 ref,实际中不需要atomic_dec(&skb_shinfo(nskb)->dataref);}tfile = rcu_dereference(tun->tfiles[txq]);

再次运行,结果就符合预期了:
在这里插入图片描述

如果 skb 发到真实网卡,当网卡中断通知发送完成,该 skb 即可得到释放,但本地环回的 skb 生命周期可是要长得多,比如 loopback_xmit,直接用 tx 路径的 skb 调用 netif_rx 了。

不出本机协议栈的数据包环回处理最好在 ndo_start_xmit 中都转交 copy 一下 skb,毕竟本地处理不要求性能(要求高性能的抓包也不用 skb),这样可避免循环依赖而伤害到协议栈的固有行为,比如我用 pdrill 几乎无法验证 collapse 机制。

多年以前我曾因为 tun 网卡没有调用 sk_mem_uncharge 导致了通过 tun 网卡的 socket 异常行为,但忘记题目了,文章找不到了。

不管这世界多么无知和操蛋,只要给我一本历史书,给我一个 TCP 疑难问题,或带我到一座可以攀登的山脚下,我就马上元气爆满!

浙江温州皮鞋湿,下雨进水不会胖。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/88738.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/88738.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【51单片机】51单片机学习笔记-课程简介

00. 目录 文章目录00. 目录01. 学习哪种类型的单片机02. 学习单片机方法03. 学习单片机硬件设备04. 学习单片机软件设备05. 学完单片机能做什么06. 附录01. 学习哪种类型的单片机 单片机的型号那么多&#xff0c;该如何选择一款合适的进行学习呢&#xff1f;这里给读者首推的当…

【Docker基础】Docker端口映射(-p参数)深度解析与实践指南

目录 前言 1 Docker网络基础 1.1 Docker网络模型概述 1.2 容器网络隔离性 2 端口映射基础 2.1 端口映射概念 2.2 为什么需要端口映射 3 -p参数详解 3.1 基本语法 3.2 四种映射格式 3.2.1 完整格式 3.2.2 省略宿主机IP 3.2.3 随机宿主机端口 3.2.4 指定协议类型 …

2、鸿蒙Harmony Next开发:ArkTS语言

目录 什么是ArkTS&#xff1f; ArkTS的发展趋势 ArkTS的定位及约束 ArkTS的对UI的拓展 1、UI描述 2、状态管理&#xff1a; ArkTS语法基础 基本知识&#xff1a;声明 基本知识&#xff1a;类型 基本知识&#xff1a;空安全 基本知识&#xff1a;类型安全与类型推断 …

【Elasticsearch】function_score

如果你希望在 Elasticsearch 查询中降低某些特定 `id` 的文档评分,可以通过 `function_score` 查询结合 `script_score` 函数来实现。`script_score` 允许你使用自定义脚本对文档的评分进行调整。 以下是一个示例,展示如何降低某些特定 `id` 的文档评分: 示例场景 假设我们…

vscode打开stm32CubeIDE的项目的注释问题

文章目录 目的是为消除红色底线打开命令面板&#xff1a;CtrlShiftP 搜索并打开&#xff1a;C/C: Edit Configurations (JSON) 修改并添加。&#xff08;注意里面的版本号&#xff09; {"configurations": [{"name": "Win32","includePath&…

ESP32使用freertos更新lvgl控件内容

LVGL不是线程安全&#xff0c;所有 lv_xxx方法只能在GUI主线程调用。 freertos都是线程池&#xff0c;子线程&#xff0c;不能直接更新lvgl&#xff0c;不然看门狗被触发&#xff0c;死机。 推荐方法案例&#xff1a; 假如搜索wifi列表得到参数是wifi_options&#xff0c;需要通…

OBOO鸥柏丨满天星(MTSTAR)多媒体信息发布系统技术解析

初次启动欢迎您使用鸥柏(OBOO)满天星(MTSTAR)多媒体信息发布系统&#xff0c;在使用本系统的独立服务器模式前&#xff0c;我们需要完成设备的一些必须设置教程技术说明。其总体流程分为两步&#xff1a;录入本地服务器IP地址->连接网络您获取到的OBOO鸥柏满天星(MTSTAR)液晶…

数据结构:栈、队列、链表

目录 栈 ​队列 链表 栈 栈数据结构特点&#xff1a;先入栈的数据后出&#xff0c;此数据结构常用的方法有&#xff1a;入栈push、出栈pop、查看栈顶元素peek等&#xff0c;下方示例以数组实现栈结构。 package com.ginko.datastructure; import lombok.extern.slf4j.Slf4j…

Python-难点-uinttest

1 需求要求&#xff1a;unittest.TestCase放在列表中&#xff0c;列表存储的是脚本文件名import使用动态加载方式&#xff1a;importlib.import_module()unittest.TestLoader使用loadTestsFromModule()2 接口3 示例4 参考资料

开源 python 应用 开发(五)python opencv之目标检测

最近有个项目需要做视觉自动化处理的工具&#xff0c;最后选用的软件为python&#xff0c;刚好这个机会进行系统学习。短时间学习&#xff0c;需要快速开发&#xff0c;所以记录要点步骤&#xff0c;防止忘记。 链接&#xff1a; 开源 python 应用 开发&#xff08;一&#xf…

ABP VNext + OpenTelemetry + Jaeger:分布式追踪与调用链可视化

ABP VNext OpenTelemetry Jaeger&#xff1a;分布式追踪与调用链可视化 &#x1f680; &#x1f4da; 目录ABP VNext OpenTelemetry Jaeger&#xff1a;分布式追踪与调用链可视化 &#x1f680;背景与动机 &#x1f31f;环境与依赖 &#x1f4e6;必装 NuGet 包系统架构概览…

C语言中整数编码方式(原码、反码、补码)

在 C 语言中&#xff0c;原码、反码、补码的运算规则与其编码特性密切相关&#xff0c;核心差异体现在符号位是否参与运算、进位如何处理以及减法是否能转化为加法等方面。以下是三者的运算规则及特点分析&#xff08;以 8 位整数为例&#xff0c;符号位为最高位&#xff09;&a…

js二维数组如何变为一维数组

在 JavaScript 中&#xff0c;将二维数组转换为一维数组&#xff08;扁平化&#xff09;有多种方法&#xff0c;可根据数组结构复杂度、性能需求和兼容性选择。以下是最常用的实现方式&#xff1a; 1. 使用 flat() 方法&#xff08;ES2019&#xff09; MDN释义&#xff1a;flat…

Claude code在Windows上的配置流程

前言 昨天在服务器上配置好了 Claude code&#xff0c;发现其编码性能和效率都非常不错。 然而&#xff0c;尝试用它修改带 UI 界面的客户端程序时颇为不便&#xff0c;因为服务器没有图形化界面&#xff0c;无法直接将应用界面直接显示到开发机上&#xff0c;调试起来颇为不…

手把手教你用YOLOv10打造智能垃圾检测系统

无需编程基础&#xff01;手把手教你用YOLOv10打造智能垃圾检测系统 垃圾分类不再难&#xff0c;AI助手秒识别 你是否曾站在分类垃圾桶前犹豫不决&#xff1f;塑料瓶是可回收还是其他垃圾&#xff1f;外卖餐盒到底该丢哪里&#xff1f;随着垃圾分类政策推广&#xff0c;这样的困…

batchnorm类

1. 伪代码&#xff1a;2. python代码&#xff1a;3. 测试&#xff1a;4. 加深理解&#xff1a;以 为例&#xff0c;x3&#xff0c;可见输出的batchnorm后y0.2627.查看模型记录的均值及方差&#xff0c;计算y0.286799&#xff0c;理解是大致这样的计算过程。&#xff08;为什么数…

SpringBoot项目保证接口幂等的五种方法!

1. 幂等概述 1.1 深入理解幂等性 在计算机领域中&#xff0c;幂等&#xff08;Idempotence&#xff09;是指任意一个操作的多次执行总是能获得相同的结果&#xff0c;不会对系统状态产生额外影响。在Java后端开发中&#xff0c;幂等性的实现通常通过确保方法或服务调用的结果…

SQL新手入门详细教程和应用实例

SQL(Structured Query Language)是用于管理和操作关系型数据库的标准语言。它允许你创建、查询、更新和删除数据。本教程将从基础概念开始,逐步引导你上手SQL,并提供详细的应用实例。教程基于标准SQL语法,实际使用时需根据数据库系统(如MySQL、SQLite或PostgreSQL)调整。…

DVWA-LOW级-SQL手工注入漏洞测试(MySQL数据库)+sqlmap自动化注入-小白必看(超详细)

首次使用DVWA的靶场&#xff0c;咋们先从最低级别的LOW开始&#xff0c;因为之前玩过一下墨者学院&#xff0c;对sql注入有一点认识和理解&#xff0c;所以先从sql的盲注开始&#xff1b; 1、测试注入点是否存在sql注入的漏洞&#xff1b; &#xff08;1&#xff09;首先我们…

JAVA线程池详解+学习笔记

1.线程池基础概念线程池是一种资源复用技术&#xff0c;通过预先创建并管理一组线程&#xff0c;减少频繁创建和销毁线程的开销。核心思想与数据库连接池、字符串常量池类似&#xff0c;旨在提升系统性能。核心参数解析ThreadPoolExecutor构造函数包含7个关键参数&#xff1a;c…