linux内核中如何修改skb报文

2025-12-09 07:11:32

前言

在内核开发中,我们很多时候需要修改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一起学习交流

女巫汤意面怎么做
6月29日是什么星座 巨蟹座性格特点