重构skb包中的检验和的计算

本文详细介绍skb包中校验和字段的计算方法,包括skb_checksum、ip_fast_csum及csum_tcpudp_magic三个核心函数的使用,并指出构造skb包时需要注意的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作者: 和 linux 相关

简介:这是重构skb包中的检验和的计算的详细页面,介绍了和linux,有关的知识、技巧、经验,和一些linux源码等。

   在之前看过那篇对于sk_buff结构介绍的文章后,对skb包中各字段有了比较深的认识吧,这两天又看了如何构造skb包的两个帖子,也在虚拟机上实现了一把,但在这里不讨论实现的具体流程,只说一下在自己构造skb包时各个检验和字段的计算。
   其中主要涉及三个函数:
skb_checksum(const struct sk_buff *skb, int offset,
int len, __wsum csum)
四个参数解释:
skb:不用说了
offset:IP Header长度
len:IP payload长度
csum:0,计算校验和时为0
ip_fast_csum(const void *iph, unsigned int ihl)
iph: ip头部首地址
ihl: ip头部长度/4,也即是iph->ihl字段的值

csum_tcpudp_magic(__be32 saddr, __be32 daddr,
unsigned short len,
unsigned short proto,
__wsum sum)

saddr:源IP
daddr:目的IP
proto:传输协议
sum:IP payload校验和,在使用中我是用的skb->csum字段的值,在网上也见到一种是通过csum_partial((char *)udph,udp_len,0),感觉上应该跟第一种计算方式是等价的,有待验证。

第一个是计算TCP、UDP payload校验和,也就是skb->csum字段,第二个是计算ip头部检验和,iph->check,后者是计算整个IP payload校验和,也就是tcp->check。
 
在构造流程中的计算如下:
skb->csum = skb_checksum()
iph->check = ip_fast_csum()
udph->check = csum_tcpudp_magic(
 
两个注意的问题:
因为校验和不涉及链路层,如果skb是直接从网卡驱动取出来的话,就需要把skb->data设置为iph
在调用skb_checksum以前必须设置udph->check为0。这是协议规定的

 

关于如何重新构造skb包的文档地址如下:

http://linux .chinaunix.net/bbs/viewthread.php ?tid=1152885&extra=page%3D3%26amp%3Bfilter%3Ddigest

http://blog.chinaunix.net/u2/73067/showart_2254716.html

<think>我们正在讨论Linux内核网络子系统中的`sk_buff`结构体操作。用户特别询问了`skb_put`和`skb_pull`函数在操作`sk_buff`时内部指针的变化。我们将结合用户提供的引用内容(关于`skb_reserve`)来类比解释。 首先,回顾`sk_buff`的基本结构。根据引用[2],`sk_buff`是内核中用于网络数据处理的核心数据结构,它含控制信息和数据缓冲区的指针。数据缓冲区通过几个关键指针管理: - `data`: 指向当前协议层数据的起始位置。 - `tail`: 指向当前协议层数据的末尾(即下一个可写位置)。 - `head`: 指向整个数据缓冲区的起始位置(分配时的起始点)。 - `end`: 指向整个数据缓冲区的结束位置(分配时的结束点)。 现在,我们分别解释`skb_put`和`skb_pull`: ### 1. `skb_put` 函数 - **功能**:在数据缓冲区的尾部添加数据(通常是在应用层或传输层添加负载)。它会使数据的长度增加。 - **指针变化**: - `tail`指针向后移动(增加)指定的长度。 - `len`(数据的总长度)也会增加相同的长度。 - **注意**:调用`skb_put`之前必须确保缓冲区尾部有足够的空间(通过`skb_tailroom`函数检查)。 - **函数原型**(简化): ```c unsigned char *skb_put(struct sk_buff *skb, unsigned int len); ``` 返回的是添加数据之前的`tail`指针位置,这样用户可以直接向这个位置写入数据。 - **示意图**: 假设初始状态: ``` head end | | v v [-----------------==================] ^ ^ data tail (其中,`-`表示未使用的头部空间,`=`表示有效数据) ``` 调用`skb_put(skb, len)`后: ``` head end | | v v [-----------------======================] ^ ^ data (tail移动后) ``` 即,`tail`向后移动了`len`,有效数据区域扩展到了尾部。 ### 2. `skb_pull` 函数 - **功能**:从数据缓冲区的头部移除数据(通常是在接收数据时,当上层协议处理完头部后,将头部“剥离”)。它会使数据的长度减少。 - **指针变化**: - `data`指针向后移动(增加)指定的长度。 - `len`减少相同的长度。 - **注意**:调用`skb_pull`之前必须确保要移除的头部长度不超过当前数据的长度(即不能拉出超过有效数据区域)。 - **函数原型**(简化): ```c unsigned char *skb_pull(struct sk_buff *skb, unsigned int len); ``` 返回的是移动后的`data`指针(即新的数据起始位置)。 - **示意图**: 初始状态同`skb_put`的初始状态: ``` head end | | v v [-----------------==================] ^ ^ data tail ``` 调用`skb_pull(skb, len)`后: ``` head end | | v v [-----------------==================] ^ ^ data tail ``` 即,`data`向后移动了`len`,有效数据区域从新的`data`开始,长度减少了`len`。 ### 3. 与`skb_reserve`的对比 用户提供的引用[1]中描述了`skb_reserve`函数: - 它在数据缓冲区的头部保留空间(通常用于添加协议头)。 - 指针变化:`data`和`tail`同时向后移动指定的长度(即整个有效数据区域向后移动,在头部留出空白)。 - 注意:它不改变数据的长度(`len`),因为只是预留空间,还没有添加实际数据。 ### 总结 - `skb_put`:在尾部扩展数据,移动`tail`指针,增加长度。 - `skb_pull`:从头部移除数据,移动`data`指针,减少长度。 - `skb_reserve`:在头部预留空间,同时移动`data`和`tail`指针,不改变长度。 这三个函数都是通过移动指针来调整数据的有效数据区域,而不需要实际移动内存中的数据,因此效率很高。 ### 代码示例(概念性) ```c // 创建一个sk_buff,假设分配了足够大的缓冲区 struct sk_buff *skb = alloc_skb(size, GFP_KERNEL); // 在头部预留空间(例如为IP头预留) skb_reserve(skb, IP_HEADER_LEN); // 此时:data和tail都指向缓冲区起始位置+IP_HEADER_LEN处,len=0 // 添加TCP负载(假设负载长度为100) unsigned char *payload = skb_put(skb, 100); // 此时:tail向后移动100,len=100 // 现在添加IP头(将预留的空间用起来) skb_push(skb, IP_HEADER_LEN); // 注意:skb_push是另一个函数,在头部添加数据 // 此时:data向前移动IP_HEADER_LEN(回到之前预留的位置),len=100+IP_HEADER_LEN // 当数据被上层处理时,剥离IP头 skb_pull(skb, IP_HEADER_LEN); // 此时:data向后移动IP_HEADER_LEN(指向TCP头),len=100 ``` 注意:这里提到了`skb_push`,它和`skb_put`类似,但是是在头部添加数据(移动`data`指针向前,并增加长度)。用户的问题虽然没有问`skb_push`,但为了完整性也一并提及。 ### 相关问题 §§相关问题§§ 1. `skb_push`函数是如何操作`sk_buff`指针的? 2. 如何检查`sk_buff`中头部空间(headroom)和尾部空间(tailroom)的大小? 3. 在发送数据时,`skb_reserve`通常用在什么场景?</think>### `skb_put` 和 `skb_pull` 在 Linux 内核 `sk_buff` 中的指针操作详解 在 Linux 内核网络子系统中,`sk_buff` 是管理网络数据的核心数据结构。`skb_put` 和 `skb_pull` 是两个关键函数,用于操作数据缓冲区(data buffer)的指针。下面结合 `sk_buff` 结构体的关键字段和用户提供的引用内容进行逐步解析: --- #### 1. `sk_buff` 的关键指针字段 在理解函数行为前,需明确 `sk_buff` 的四个核心指针(如图示): ```c struct sk_buff { unsigned char *head; // 缓冲区起始地址(固定) unsigned char *data; // 当前协议层数据的起始地址 unsigned char *tail; // 当前有效数据的结束地址 unsigned char *end; // 缓冲区结束地址(固定) unsigned int len; // 当前有效数据长度 }; ``` 数据缓冲区布局: ``` head end | | v v [--------------------------------] // 分配的完整缓冲区 ^ ^ | | data tail |<----- len --->| ``` (`head` 和 `end` 固定,`data` 和 `tail` 随操作移动)[^2] --- #### 2. `skb_put()`:在尾部扩展数据 - **功能**:在数据**尾部添加新数据**(如添加负载)。 - **指针变化**: - `tail` 向后移动 `len` 字节 - `len` 增加 `len` 字节 - **函数逻辑**(简化): ```c void *skb_put(struct sk_buff *skb, unsigned int len) { void *tmp = skb->tail; skb->tail += len; // 移动 tail 指针 skb->len += len; // 增加有效数据长度 return tmp; // 返回添加数据的位置 } ``` - **操作示意图**: ``` 初始状态: head data tail end | | | | [------------][有效数据][空闲空间] |<--len-->| 调用 skb_put(skb, X) 后: head data tail end | | | | [------------][有效数据][新数据][空] |<----- len+X ---->| ``` ▶ 仅移动 `tail` 和更新长度,不修改数据内容(由调用者填充新数据)[^1]。 --- #### 3. `skb_pull()`:从头部移除数据 - **功能**:从数据**头部移除数据**(如剥离协议头)。 - **指针变化**: - `data` 向后移动 `len` 字节 - `len` 减少 `len` 字节 - **函数逻辑**(简化): ```c void *skb_pull(struct sk_buff *skb, unsigned int len) { skb->data += len; // 移动 data 指针 skb->len -= len; // 减少有效数据长度 return skb->data; // 返回新的数据起始位置 } ``` - **操作示意图**: ``` 初始状态: head data tail end | | | | [空闲空间][协议头][有效数据][空闲] |<----- len ----->| 调用 skb_pull(skb, X) 后(移除协议头): head data tail end | | | | [空闲空间][协议头][有效数据][空闲] |<--len-X-->| ``` ▶ 仅移动 `data` 指针并更新长度,不实际清除内存[^1]。 --- #### 4. 与 `skb_reserve()` 的对比 用户提供的引用[1]中提到的 `skb_reserve()` 是**预留头部空间**的操作: ```c static inline void skb_reserve(struct sk_buff *skb, unsigned int len) { skb->data += len; skb->tail += len; // 同时移动 data 和 tail } ``` - **区别**: - `skb_reserve`:在缓冲区**头部预留空间**(为后续添加协议头准备),不改变 `len`。 - `skb_put`:在**尾部扩展数据**,增加 `len`。 - `skb_pull`:从**头部移除数据**,减少 `len`。 --- #### 5. 典型工作流示例 发送数据时的常见操作序列: ```c struct sk_buff *skb = alloc_skb(total_size, GFP_KERNEL); skb_reserve(skb, ETH_HLEN + IP_HLEN); // 预留以太网+IP头空间 skb_put(skb, payload_len); // 添加负载数据 memcpy(skb->data, payload, payload_len); // 填充负载 add_ip_header(skb); // 添加IP头(移动data指针) add_eth_header(skb); // 添加以太网头(移动data指针) dev_queue_xmit(skb); // 发送 ``` 接收数据时的操作: ```c skb_pull(skb, ETH_HLEN); // 剥离以太网头 skb_pull(skb, IP_HLEN); // 剥离IP头 process_payload(skb->data, skb->len); // 处理负载 ``` --- ### 总结 - `skb_put`:尾部扩展数据 → **`tail` 后移**,**`len` 增加** - `skb_pull`:头部移除数据 → **`data` 后移**,**`len` 减少** - 共同点: - 均通过**指针移动**而非内存拷贝实现高效操作[^1][^2] - 不修改 `head` 和 `end`(缓冲区边界固定) - 操作后需由调用者填充/读取数据 这些操作是 Linux 网络栈高性能的关键设计,避免了数据处理中的内存复制开销。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值