如何实现自定义sk_buff数据包并提交协议栈

目录

一、自定义数据包的封装流程

1. 分配skb

2.初始定位(skb_reserve)

3.拷贝数据(skb_push / skb_pull / skb_put / )

4.设置传输层头部

5.设置IP层头部

6.添加以太网头

二、自定义数据包的封装实例

1. “纯净数据包”发送到本机的协议栈并交由上层处理:

2. “完整的IP数据包”发送到本地的协议栈并交由上层处理:

3. “纯净数据包”通过网卡发送到网络上的其他主机:


现在有一个需求:我需要从IO内存(总线上)读取数据,然后将读取到的数据发送到协议栈,由协议栈将数据发送到应用层。这里的数据既包括完整的IP数据包,也包括一些两个IP头的报文,用来实现数据的透传。

其实数据的类型并不是难点,我们先可以当做一个纯数据内容来处理,如果我们要发送至协议栈,需要分别封装UDP头,IP头等头部信息如果我们要通过网卡发送到网络中,则需要在UDP头,IP头的基础上再封装一个以太网头

还有我在实现的时候用的是字符设备驱动,也就是说在字符设备驱动里实现网卡驱动的部分功能,至于为什么不同网卡驱动呢,是因为我们的有多个网口且没有采用一个驱动对应多个网口的模式,而是采用一对一的最原始方式,但是数据的读取转发需要做统一处理;除此之外还有个原因是网络设备没有设备节点,因此无法通过访问文件系统的那一套接口访问网络设备(使用的是另一套套接字编程接口)。因此决定用字符设备驱动来实现网卡驱动的部分内容。下面进入主题:

一、自定义数据包的封装流程

在设备驱动中如何自己封装一个报文并发送到应用层或者通过网卡发送到网络中呢?

我们首先应该知道的时在网络协议栈中,网络数据时基于sk_buff在不同TCP/IP协议栈之间进行传播的,而自己封装报文其实就是自己构造一个sk_buff的数据,然后将其按需求进行网络数据转发即可。接下来介绍封装sk_buff的步骤:

1. 分配skb

分配sk_buff空间一般是由alloc_skb()函数来完成的,alloc_skb()会分配一个套接字缓区和一个数据缓冲区,其中参数len为数据缓冲区的大小。除此之外还有个dev_alloc_skb()也可以用于skb空间的分配。

分配时:*head, *data, *tail同时指向分配的数据空间的起始地址

*end 指向分配的数据空间的结束地址

2.初始定位(skb_reserve)

skb_reserve()函数的作用就是为即将存储的数据提供头部空间,也就是说给数据包添加头部信息预留必要的空间。我们知道网络数据包在协议栈中传递时为了高效处理,不进行数据的拷贝,而层与层之间只传递相应的头部指针信息。skb_reserve()便是用来一层层的添加头部数据的。

skb_reserve(skb, len); 函数调用后,*data,和*tail 两个指针分别会向后移动len个字节,而预留的这len个字节就是为了预留足够的空间,共后续函数存储相应的数据或者头部信息。

3.拷贝数据(skb_push / skb_pull / skb_put / )

一般情况下,拷贝的数据有两种格式

  1. 一种是数据本身已经包含完整的头部信息,也就是说是一个完整的IP数据包
  2. 另一种是数据不包含各层的头部信息,它只是我们需要的”纯净的数据”。

不同的数据格式的处理方法不同,也就用到了不同的处理函数。我们分别进行介绍:

  • 如果数据包含完整的头部信息(IP头,UDP头(TCP头)),此时我们没必要进行从基础的数据开始进行一层一层的填充,只需要将整个数据包全部复制过来即可,也就不需要后续添加各种头部信息的处理了。此时我们是从数据包的头到尾进行填充的(复制的),因此相对于分配的skb来说添加的是尾部信息(将整个数据报文作为尾部)所以我们使用的是加尾的skb_put()函数。skb_put(skb, len); 的作用是将*tail指针向后移动len个字节用以填充尾部数据。
skb_reserve(skb,2);  /* head room  */

/*填充IP 数据包*/

skb_put(skb,dataLen);

memcpy(skb->data, data, dataLen);
  • 如果应用层传递的数据(当然,也不一定是应用层传递的数据)就是”纯净的数据”,不包含任何头部信息,那么我们需要一层一层的添加协议头部信息。而在此时,我们最先要做的就是将”纯净的数据”填充到我们的skb中。由于我们已经通过skb_reserve()预留了足够空间的用来存报文的区域,引用我们可以通过加头的方式进行数据包的封装。skb_push()就是通过将data指针前移len个长度,而这len个长度便可以填充相应的数据或者协议头部信息(入栈)。
/*填充数据*/

skb_push(skb,dataLen);  

memcpy(skb->data, data, dataLen); 

4.设置传输层头部

将传输层的头部添加到skb的头部上(入栈), 这里我是用的是UDP头(struct udphdr* );

/*填充UDP*/

skb_push(skb,sizeof(struct udphdr));

skb->h.uh    = skb->data;

udph         = skb->data;

udph->source  = htons(0x1f91);

udph->dest    = htons(0x1f90);

udph->len     = htons(dataLen+sizeof(struct udphdr));

udph->check   = 0;    /*if not check,  must be 0*/

//udph->check = ip_compute_csum(skb->data, dataLen+sizeof(struct udphdr));

这里需要注意的一点是:

       如果计算机不检查UDP的校验和,那就直接将其设置为0(两个字节都是0)否则会出错,如果要检查UDP的校验和,那么必须的得计算校验和。  

        因为我在测试期间通过将数据包从开发板网卡发送至PC的调试助手上时发现wirkshark可以抓到发出的数据,但是调试助手上就是没有任何反应,百度了一下,发现很多人也遇到过。这里吧,需要关注几点:

  • 1. 最好把电脑的防火墙功能关闭,我不敢说一定能解决,或者说根本解决不了,引用wirkshark的抓包点可能已经在防火墙之后了(还没验证确认)。
  • 2. 更多的时候是自己的数据报文校验和出了问题,我曾尝试发送一个几乎完全一致的报文到电脑,电脑上的调试助手还是没有收到,从另一台电脑调试助手发出的则没有问题,之所以说几乎完全一样,是因为电脑发出的IP头里的ID每次都变化,我只能保证其中的一次一样,但是问题是wirkshark会显示红色(校验和计算的不正确),后来还是感觉是校验和的问题,虽然中间也试过自己计算校验和,wireshark抓包显示报文一切正常,但是应用层还是收不到数据的情况。 最后把电脑的UDP校验和,IP校验和检查全部给禁用,然后在驱动中不再计算UDP的校验和,直接填充为0(如果不计算必须填充为0),但是IP的校验和依然计算,然后再次尝试的时候,应用层也就是调试助手便可以收到了驱动发出的自定义的数据报文。(禁用校验和是在网络连接—>右键“属性”—>”配置”菜单界面)

5.设置IP层头部

将网络层的IP头添加到skb的头部上(入栈) 。

/*填充IP*/
skb_push(skb,sizeof(struct iphdr));
skb->nh.iph    =   skb->data;
iph            =   skb->data;6.设置以太帧头部
iph->version   =   4;
iph->ihl       =   sizeof(struct iphdr)>>2;//5
iph->tos       =   0;
iph->tot_len   =   htons(0x30);  /*TODO*/ /*报文长度*/
iph->id        =   1;
iph->frag_off  =   0;
iph->ttl       =   0x80;
iph->protocol  =   0x11;
iph->saddr     =   htonl(addr);
iph->daddr     =   htonl(dstIP);
iph->check     =   0;   /*TODO*/
iph->check     =   ip_fast_csum(skb->nh.iph, skb->nh.iph->ihl);

6.添加以太网头

    填充以太网头主要是为了向网络中其他的主机发送IP报文,一般情况下,底层的驱动设备可以自动填充以太网头部信息,比如使用原始套接字发送icmp信息时,我们不需要手动填充以太网头。在这个需求中,我自己手动添加了以太网头部信息,然后便可以通过xmit函数将数据包发送出去。如果只是发送到本地的应用层进行处理,则不需要进行以太网头部信息的填充,原因就是该报文是在网络层产生的,直接提交至协议栈即可,以太网头是工作在数据链路层的。

/*填充MAC*/
/*if dev_queue_xmit, mac is need*/
skb_push(skb,sizeof(struct ethhdr));
skb->mac.raw    =  skb->data;
ethdr           =  skb->data;
memcpy(ethdr->h_dest, pcMac, 6);
memcpy(ethdr->h_source, loMac, 6);
ethdr->h_proto  = htons(0x0800);

二、自定义数据包的封装实例

1. “纯净数据包”发送到本机的协议栈并交由上层处理:

int static e1_dev_netif_rx_data(char *data, int len)
{
 	struct sk_buff    *skb=NULL;
	struct net_device *dev;
	struct udphdr   *udph;
	struct iphdr    *iph;
	struct ethhdr   *ethdr;
	struct in_device* in_dev;
    int i;
	u32 addr,mask;	/*20181107*/
	u32 addr2;
	
	int dataLen = len;
	int length  = sizeof(struct iphdr)+sizeof(struct udphdr)+dataLen+10;
    /*  要分配的空间最少为数据长度dataLen + IP头 + UDP头 */

	if(!data || len<=0){
		return -1;
	}

    /*获取eth0接口,只是为了使用eth0的IP,用来填充IP的头部信息, 指定网卡适配器*/
	if((dev = __dev_get_by_name("eth0")) == NULL){
		printk("get dev fail\n");
	}else{
		in_dev = (struct in_device*)dev->ip_ptr;
		addr = in_dev->ifa_list->ifa_local;
		mask = in_dev->ifa_list->ifa_mask;
    }
	
	if(!(skb = dev_alloc_skb(length))){
		printk("dev_alloc_skb malloc skb error\n");
		return -1;
	}

	skb_reserve(skb,length);  /* head room  */
	skb->len =  0;
	
    /*填充数据*/
	skb_push(skb,dataLen);   
	memcpy(skb->data, data, dataLen);  


    /*填充UDP*/
	skb_push(skb,sizeof(struct udphdr));
	skb->h.uh  = skb->data;
	udph       = skb->data;


    /*填充IP*/
	skb_push(skb,sizeof(struct iphdr));
	skb->nh.iph  = skb->data;
	 iph         = skb->data;


	skb->len  = sizeof(struct iphdr)+sizeof(struct udphdr)+dataLen;

	udph->source = htons(0x1f91);
	udph->dest   = htons(0x1f90);
	udph->len    = htons(dataLen+sizeof(struct udphdr));
	udph->check  = 0;    /*if not check,  must be 0*/
	//udph->check  = ip_compute_csum(skb->data, dataLen+sizeof(struct udphdr));

    iph->version = 4;
	iph->ihl     =  sizeof(struct iphdr)>>2;//5
	iph->tos     = 0;
	iph->tot_len = htons(0x30);  /*TODO*/ /*报文长度*/
	iph->id      = 1;
	iph->frag_off= 0;
	iph->ttl     = 0x80;
	iph->protocol= 0x11;
	iph->saddr   = htonl(addr+1);  /*random, but src addr can't be same with dst addr*/
	iph->daddr   = htonl(addr);
	iph->check   = 0;   /*TODO*/
	iph->check   = ip_fast_csum(skb->nh.iph, skb->nh.iph->ihl);
  

	skb->protocol = htons(ETH_P_IP); /*指定协议类型为IP报文*/

    /* PACKET_HOST      : 发送给本地的数据包     */
    /* PACKET_OTHERHOST : 发送给其他主机的数据包 */
	skb->pkt_type =  PACKET_HOST;/* 发送本地的应用层,因此使用PACKET_HOST *//*must set*/
	skb->dev      = dev;  /* 必须指定通过哪个网卡来发送,此程序为eth0*/
                          /* dev = __dev_get_by_name("eth0")*/

    /*调试信息*/
	for(i=0;i<skb->len;i++){
		if(i%16==0){
			printk("\n");
		}
		printk("%2.2x  ", skb->data[i]);
	}
	printk("\n");

	if(netif_rx(skb)==NET_RX_SUCCESS){  /* netif_rx(skb): 用于将报文发送至应用层*/
		printk("e1_dev_netif_rx_data send pkt success\n");
	}

}

2. “完整的IP数据包发送到本地的协议栈并交由上层处理:

int static e1_dev_netif_rx_rawdata(char *data, int len)
{
 	struct sk_buff    *skb=NULL;
	struct net_device *dev;
	struct udphdr     *udph;
	struct iphdr      *iph;
	struct ethhdr     *ethdr;
	struct in_device  * in_dev;
    int i;
	u32 addr,mask;	/*20181107*/
	
	int dataLen = len;
	int length  = sizeof(struct iphdr)+sizeof(struct udphdr)+dataLen+10;

	if(!data || len<=0){
		return -1;
	}

	if((dev = __dev_get_by_name("eth0")) == NULL)	{
		printk("get dev fail\n");
	}else{
	
		in_dev = (struct in_device*)dev->ip_ptr;
		addr = in_dev->ifa_list->ifa_local;
		mask = in_dev->ifa_list->ifa_mask;
	}
	
	if(!(skb = dev_alloc_skb(length))){
		printk("dev_alloc_skb malloc skb error\n");
		return -1;
	}

	skb_reserve(skb,2);  /* head room  */
	
    /*填充IP 数据包*/
	skb_put(skb,dataLen);
	memcpy(skb->data, data, dataLen);
	
	skb->protocol = htons(ETH_P_IP);
	skb->pkt_type =  PACKET_HOST;
	skb->dev      = dev;
    skb->len      = len;
	
	for(i=0;i<skb->len;i++){
		if(i%16==0){
			printk("\n");
		}
		printk("%2.2x  ", skb->data[i]);
	}
	printk("\n");

	if(netif_rx(skb)==NET_RX_SUCCESS){
		printk("e1_dev_netif_rx_rawdata send pkt success\n");

	}

}

3. “纯净数据包”通过网卡发送到网络上的其他主机:

int static e1_dev_xmit(char *data, int len, int dstIP)
{
	struct sk_buff    *skb=NULL;
	struct net_device *dev;
	struct udphdr   *udph;
	struct iphdr    *iph;
	struct ethhdr   *ethdr;
	struct in_device* in_dev;
    int i;
	u32 addr,mask,fpgaIP;	/*20181107*/
	char pcMac[] = {0x08,0x00,0x3e,0x32,0x53,0x24};
	char loMac[] = {0x08,0x00,0x3e,0x03,0x01,0x11};
	//char loMac[] = {0x3c,0x97,0x0e,0x0b,0xf9,0xf9};
	
	//char buff[]="http://www.cmsoft.cn";
	//int dataLen = sizeof(buff)-1;
	int dataLen = len;
	
	int length = sizeof(struct ethhdr)+sizeof(struct iphdr)+sizeof(struct udphdr)+dataLen+10;

	if(!data || !len){
		return -1;
	}

	if((dev = __dev_get_by_name("eth0")) == NULL)	{
		printk("get dev fail\n");
	}else{
	
		in_dev = (struct in_device*)dev->ip_ptr;
		addr = in_dev->ifa_list->ifa_local;
		mask = in_dev->ifa_list->ifa_mask;
	}
	
	if(!(skb = dev_alloc_skb(length))){
	printk("dev_alloc_skb malloc skb error\n");
	return -1;
	}

	skb_reserve(skb,length);  /* head room  */
	skb->len =  0;
	
    /*填充数据*/
	skb_push(skb,dataLen);   
	memcpy(skb->data, data, dataLen);  

	
    /*填充UDP*/
	skb_push(skb,sizeof(struct udphdr));
	skb->h.uh  = skb->data;
	udph       = skb->data;


    /*填充IP*/
	skb_push(skb,sizeof(struct iphdr));
	skb->nh.iph  = skb->data;
	 iph         = skb->data;

	skb->len  = sizeof(struct iphdr)+sizeof(struct udphdr)+dataLen;
/*填充MAC*/

    /*if dev_queue_xmit, mac is need*/
    skb_push(skb,sizeof(struct ethhdr));
	skb->mac.raw  = skb->data;
	ethdr = skb->data;
    skb->len = sizeof(struct iphdr)+sizeof(struct udphdr)+dataLen + sizeof(struct ethhdr);

    memcpy(ethdr->h_dest,   pcMac, 6);
    memcpy(ethdr->h_source, loMac, 6);
	ethdr->h_proto = htons(0x0800);


	udph->source = htons(0x1f91);
	udph->dest   = htons(0x1f90);
	udph->len    = htons(dataLen+sizeof(struct udphdr));
	udph->check  = 0;    /*if not check,  must be 0*/
	//udph->check  = ip_compute_csum(skb->data, dataLen+sizeof(struct udphdr));

    iph->version = 4;
	iph->ihl     =  sizeof(struct iphdr)>>2;//5
	iph->tos     = 0;
	iph->tot_len = htons(0x30);  /*TODO*/ /*报文长度*/
	iph->id      = 1;
	iph->frag_off= 0;
	iph->ttl     = 0x80;
	iph->protocol= 0x11;
	iph->saddr   = htonl(addr);
	iph->daddr   = htonl(dstIP);
	iph->check   = 0;   /*TODO*/
	iph->check   = ip_fast_csum(skb->nh.iph, skb->nh.iph->ihl);
  

	skb->protocol = htons(ETH_P_IP);
	skb->pkt_type =  PACKET_OTHERHOST;//;PACKET_OTHERHOST

	skb->dev = dev;

	for(i=0;i<skb->len;i++){
		if(i%16==0){
			printk("\n");
		}
		printk("%2.2x  ", skb->data[i]);
	}
	printk("\n");

	dev_queue_xmit(skb);

}

 

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
在 Linux 内核网络协议栈中,sk_buff 是一个非常重要的数据结构,用于存储网络数据包及其相关信息。sk_buff 结构体定义在 include/linux/skbuff.h 头文件中,它的定义如下: ``` struct sk_buff { /* 网络数据包 */ struct sk_buff *next; struct sk_buff *prev; ktime_t tstamp; struct sock *sk; struct net_device *dev; struct net_device *real_dev; struct net_device *vlan_dev; unsigned char cb[48]; /* 网络数据包数据部分 */ unsigned int len, data_len; __u16 mac_len, hdr_len; union { __wsum csum; struct { u16 csum_start; u16 csum_offset; }; }; unsigned char *head, *data; struct skb_shared_info *shinfo; /* 与协议栈相关的信息 */ unsigned int truesize; atomic_t users; unsigned char *tail; unsigned char *end; unsigned int frag_list_len; struct sk_buff *frag_list; skb_frag_t frags[MAX_SKB_FRAGS]; struct skb_ext *ext; }; ``` 下面是一些重要的字段及其含义: - next/prev:指向链表中下一个/上一个 sk_buff 结构体,用于构成链表结构,例如网络驱动程序的接收队列。 - tstamp:记录从协议栈接收到网络数据包的时间戳。 - sk:指向与这个网络数据包相关的套接字,用于实现协议栈和应用层之间的通信。 - dev:指向接收或发送这个网络数据包的网络设备结构体。 - len/data_len/mac_len/hdr_len:表示网络数据包的长度、负载长度、MAC 头长度和协议头长度。 - csum/csum_start/csum_offset:表示网络数据包的校验和值、校验和计算开始位置和偏移量。 - head/data/tail/end:指向网络数据包的头部、负载、尾部和结束位置。 - frag_list/frag_list_len/frags:表示网络数据包的分片信息。 - truesize:表示 sk_buff 结构体的大小。 - users:表示 sk_buff 结构体的引用计数。 - shinfo:指向一个共享信息结构体,用于记录一些共享的数据。 总之,sk_buff 结构体非常重要,它是整个协议栈中重要的数据缓存。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叨陪鲤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值