linux内核中如何修改skb报文

前言

  • 在内核开发中,我们很多时候需要修改linux网络数据包的内容。那么怎样修改skb报文才正确?这个问题在网上的资料讲解的不是很全,下面是我这几天梳理的步骤

skb修改数据包流程

-内核代码中有许多用于计算校验和的API,下面是linux网络技术内幕相关API的截图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

修改数据包的流程分析和相关api的介绍

  • 在内核中构造数据包的时候,我们需要关注三个校验和:分别是sk_buf中的csum,ip_summed,ip头部中的check和udp或者tcp头部中的check
    用于计算校验和的API:L3校验和的计算比L4的校验和要快得多,因为它只包含IP报头。校验和的API都在checksum.h中。
    TODO:skb->csum:存放硬件或者软件计算的payload的checksum不包括伪头,但是是否有意义由skb->ip_summed的值决定

下面来对这三个字段来进行说明

Sk_buf中的csum字段
  • 该字段代表的是以太网发送数据包时,在将数据从用户控件复制到内核空间时,以相应算法计算数据包检验和,存放于 csum
    接受数据包时,csum 存放网络设备计算的检验和。linux内核对于ip或者tcp或者udp的校验和都是采用的同一校验的方法(累加再进行取反)。因为网络数据包可能进行分片,那么linux内核就将校验和分为了两个函数,分别是累加csum_partial和取反csum_fold
    我们可以使用csum_partial(const void *buff, int len, __wsum sum)来计算。使用例子:skb->csum = csum_partial((unsigned char *)tcph, ntohs(iph->tot_len) - iph_lenip_hdrlen(skb), 0)
Sk_buf中的ip_summed字段

ip_summed字段表示L4层的校验和状态。根据报文的不同(输入报文和输出报文),ip_summed会有不同的取值。
当数据包为输入报文:
#define CHECKSUM_NONE 0
#define CHECKSUM_UNNECESSARY 1
#define CHECKSUM_COMPLETE 2


skb->csum:存放硬件或者软件计算的payload的checksum不包括伪头,但是是否有意义由skb->ip_summed的值决定。

CHECKSUM_NONE表示csum域中的校验值是无意义的,需要L4层自己校验payload和伪头。有可能是硬件检验出错或者硬件没有校验功能,协议栈软件更改如pskb_trim_rcsum函数。

CHECKSUM_UNNECESSARY表示网卡或者协议栈已经计算和验证了L4层的头和校验值。也就是计算了tcp udp的伪头。还有一种情况就是回环,因为在回环中错误发生的概率太低了,因此就不需要计算校验来节省cpu事件。

CHECKSUM_COMPLETE表示网卡已经计算了L4层payload的校验,并且csum已经被赋值,此时L4层的接收者只需要加伪头并验证校验结果。


· 1) 在L4层发现如果udp->check位段被设为0,那么skb->ip_summed直接设为CHECKSUM_UNNECESSARY,放行该报文。


· 2) 如果skb->ip_summed为CHECKSUM_COMPLETE,则把skb->csum加上伪头进行校验,成功则将skb->ip_summed设为CHECKSUM_UNNECESSARY, 放行该数据包。


· 3) 通过上述后skb->ip_summed还不是CHECKSUM_UNNECESSARY,那么重新计算伪头赋给skb->csum。


· 4) 将还不是CHECKSUM_UNNECESSARY的数据报文的payload加上skb->csum进行checksum计算,成功将设为CHECKSUM_UNNECESSARY并放行,失败则丢弃。

  • 当数据包为输出报文:
    · skb->csum表示为csum_start和csum_offset,它表示硬件网卡存放将要计算的校验值的地址,和最后填充的便宜。这个域在输出包时使用,只在校验值在硬件计算的情况下才对于网卡真正有意义。硬件checksum功能只能用于非分片报文。
    · 而此时ip_summed可以被设置的值有下面两种:
    · #define CHECKSUM_NONE 0
    · #define CHECKSUM_PARTIAL 3

    · CHECKSUM_NONE 表示协议栈计算好了校验值,设备不需要做任何事。
    CHECKSUM_PARTIAL表示协议栈算好了伪头需要硬件计算payload checksum。
ip头部中的check字段
  • IP的头部校验和是用来检测IP头部的完整性和正确性,数据的完整性是高层协议校验的,比如TCP/UDP(多数LV4的校验和是包含报头和数据的)。数据包在二层有检验,在三层也有校验,在4层也是存在校验的。
    IP层的校验和函数使用ip_fast_csum函数。该函数的参数是ip报头的指针及其长度。返回值就是检验和。在计算校验和的时候应该先将ip头部的check字段设置为0

L4层check字段

  • TCP和UDP协议所计算的校验和会包括其报头、有效负载以及所谓的伪报头。伪报头基本上就是一个区块,为了方便起见,其中的字段是从IP报头中取来的,换言之,IP头部中出现的一些信息最后会整合到L4检验和中。注意:伪报头只是为了计算校验和而定义;伪报头并不存在于网络中的传输的封包内。
    因为L4层的校验和会用到L3层的头部信息,所以改变了L3层的头部,最好再次计算一下校验和
    TCP和UDP的校验和主要用到的函数为csum_tcpudp_magic
    说明:对于TCP而言,我们可以采用更加上层的函数,例如tcp_v4_check。该函数在内核中用两种调用方式,这两种情况可以查看__tcp_v4_send_check。一种是只计算伪首部,另一种是计算完成的TCP校验和。采用何种方式取决于ip_summed的值
    TODO: 下面这两种情况分别对应着软件校验和以及硬件校验和
static void __tcp_v4_send_check(struct sk_buff *skb,
				__be32 saddr, __be32 daddr)
{
	struct tcphdr *th = tcp_hdr(skb);

	if (skb->ip_summed == CHECKSUM_PARTIAL) {
		th->check = ~tcp_v4_check(skb->len, saddr, daddr, 0);
		skb->csum_start = skb_transport_header(skb) - skb->head;
		skb->csum_offset = offsetof(struct tcphdr, check);
	} else {
		th->check = tcp_v4_check(skb->len, saddr, daddr,
					 csum_partial(th,
						      th->doff << 2,
						      skb->csum));
	}
}
  • 说明:当我们修改数据包的时候,需要注意一下几个字段需要做相应的调整。我们首先来看ip头部信息中的tot_len字段,该字段ip头部加数据段。我们如果修改了数据包的长度,我们就需要更新该字段。同时,在tcp的头部信息中没有长度字段,所以我们不用更新tcp的长度字段。但是如果我们修改的udp的报文,我们需要修改udp头部中的长度(udp头部中有udp数据包长度字段)。如果我们没有调用内核提供的api函数来操作skb,那么我们需要手动修改skb中的head、tail、以及len字段。

总结:当我们修改数据包的时候,需要关注udp/tcp校验和以及ip头部校验和计算,还需要修改ip头部中的tot_len字段。以及设置skb中的check和ip_summed。如果我们不是调用内核的api函数,例如:skb_put,skb_push等来操作skb。那么我们还需要手动修改skb中的head、tail以及len。(内核api底层也是在移动这些指针和修改len)

这里只是介绍了修改数据包的方式,关于新建数据包的博客我会在后面补上。当然只有介绍方式是不行的,代码的话请参考我的另一篇博客代码连接地址https://blog.csdn.net/u011551613/article/details/107081411 欢迎大家加入QQ群610849576一起学习交流

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux内核skb是指socket buffer,它是用来在不同层级之间传递数据的数据结构。skb内核的使用非常广泛,它可以包含不同协议层的报文,例如ARP、IP等。[1] 对于skb的处理,内核使用child结构体变量来指向第二个sk_buff结构体的内存地址。在代码,可以通过(child + 1)来获取引用计数器的开始地址,进而对skb进行操作。 当需要释放skb时,内核使用kfree_skb()函数进行释放。该函数首先判断skb是否为空,如果为空则直接返回。然后,通过判断引用计数器的值,决定是否继续释放skb。如果引用计数器的值为1,表示只有当前正在使用的引用,那么就可以进行释放。否则,如果引用计数器的值不为1,则返回。释放skb的具体实现是通过调用__kfree_skb()函数来完成的。 在__kfree_skb()函数,首先调用skb_release_all()函数释放skb的头部状态,然后再调用skb_release_data()函数释放skb的数据部分。 总结来说,在Linux内核skb是用于在不同层级之间传递数据的数据结构,对于skb的处理包括分配、释放等操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [ipv4_linux内核skb处理流程图_](https://download.csdn.net/download/weixin_42666036/25945917)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [linux 内核网络 sk_buff 之申请和释放 Ⅲ](https://blog.csdn.net/wangquan1992/article/details/112603882)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [sk_buff封装和解封装网络数据包的过程详解](https://blog.csdn.net/weixin_33911824/article/details/89867585)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值