如何实现自定义sk_buff数据包并提交协议栈
目录
3.拷贝数据(skb_push / skb_pull / skb_put / )
2. “完整的IP数据包”发送到本地的协议栈并交由上层处理:
现在有一个需求:我需要从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 / )
一般情况下,拷贝的数据有两种格式:
- 一种是数据本身已经包含完整的头部信息,也就是说是一个完整的IP数据包;
- 另一种是数据不包含各层的头部信息,它只是我们需要的”纯净的数据”。
不同的数据格式的处理方法不同,也就用到了不同的处理函数。我们分别进行介绍:
- 如果数据包含完整的头部信息(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);
}
如何实现自定义sk_buff数据包并提交协议栈的更多相关文章
- Asp.Net Core 轻松学-实现跨平台的自定义Json数据包
前言 在前后端分离的业务开发中,我们总是需要返回各种各样的数据包格式,一个良好的 json 格式数据包是我们一贯奉行的原则,下面就利用 Json.Net 来做一个简单具有跨平台的序列化数据包实 ...
- IM通信协议逆向分析、Wireshark自定义数据包格式解析插件编程学习
相关学习资料 http://hi.baidu.com/hucyuansheng/item/bf2bfddefd1ee70ad68ed04d http://en.wikipedia.org/wiki/I ...
- c#网络通信框架networkcomms内核解析之八 数据包的核心处理器
NetworkComms网络通信框架序言 本文基于networkcomms2.3.1开源版本 gplv3协议 我们先回顾一个 c#网络通信框架networkcomms内核解析之六 处理接收到的二进制 ...
- [转]C#截获本机数据包方法实例
本文向大家介绍Windows Sockets的一些关于用C#实现的原始套接字(Raw Socket)的编程,以及在此基础上实现的网络封包监视技术.同Winsock1相比,Winsock2最明显的就是支 ...
- python 导入数据包的几种方法
1.直接导入整个数据包:improt 数据包 参考代码: # -*- coding:utf-8 -*- # 导入random数据包 import random # 引用random数据包中的randi ...
- Linux数据包路由原理、Iptables/netfilter入门学习
相关学习资料 https://www.frozentux.net/iptables-tutorial/cn/iptables-tutorial-cn-1.1.19.html http://zh.wik ...
- 数据包从物理网卡流经 Open vSwitch 进入 OpenStack 云主机的流程
目录 文章目录 目录 前言 数据包从物理网卡进入虚拟机的流程 物理网卡处理 如何将网卡收到的数据写入到内核内存? 中断下半部分软中断处理 数据包在内核态 OvS Bridge(Datapath)中的处 ...
- sk_buff封装和解封装网络数据包的过程详解
转自:http://www.2cto.com/os/201502/376226.html 可以说sk_buff结构体是Linux网络协议栈的核心中的核心,几乎所有的操作都是围绕sk_buff这个结构体 ...
- sk_buff封装和解封装网络数据包的过程详解(转载)
http://dog250.blog.51cto.com/2466061/1612791 可以说sk_buff结构体是Linux网络协议栈的核心中的核心,几乎所有的操作都是围绕sk_buff这个结构体 ...
随机推荐
- Spring Cloud Gateway自定义异常处理Exception Handler
版本: Spring Cloud 2020.0.3 常见的方法有 实现自己的 DefaultErrorWebExceptionHandler 或 仅实现ErrorAttributes. 方法1: Er ...
- Server-Speaks-First 有点坑,Linkerd 2.10 中的协议检测和不透明端口
协议检测(Protocol detection),顾名思义,允许 Linkerd 自动检测 TCP 连接中使用的协议. Linkerd 的设计原则之一是"just work",协议 ...
- 【Python机器学习实战】感知机和支持向量机学习笔记(三)之SVM的实现
前面已经对感知机和SVM进行了简要的概述,本节是SVM算法的实现过程用于辅助理解SVM算法的具体内容,然后借助sklearn对SVM工具包进行实现. SVM算法的核心是SMO算法的实现,首先对SMO算 ...
- Windows根据端口号查找对应的进程和服务
需求 1,我们在Win10安装一些Web服务时,会发现默认端口被占用,比如443端口被占用,808端口被占用,那么如何找出占用这些默认端口的进程和对应的服务呢? 2,系统安装完成后,会有一些应用对外开 ...
- MySQL学习01(初识MySQL)
初识MySQL 只会写代码的是码农:学好数据库,基本能混口饭吃:在此基础上再学好操作系统和计算机网络,就能当一个不错的程序员.如果能再把离散数学.数字电路.体系结构.数据结构/算法.编译原理学通透,再 ...
- 《微服务架构设计模式》读书笔记 | 第4章 使用Saga管理事务
目录 前言 1. 微服务架构下的事务管理 1.1 分布式事务的挑战 1.2 一个Saga的示例 1.3 Saga使用补偿事务来回滚所作出的改变 2. Saga的协调模式 2.1 两种Saga协调模式 ...
- Python Flask API实现方法-测试开发【提测平台】阶段小结(一)
微信搜索[大奇测试开],关注这个坚持分享测试开发干货的家伙. 本篇主要是对之前几次分享的阶阶段的总结,温故而知新,况且虽然看起来是一个小模块简单的增删改查操作,但其实涉及的内容点是非常的密集的,是非常 ...
- JavaSE-基础语法
注释 单行注释: //注释 多行注释: /*注释*/ 文档注释: /** 文档注释 */ 标识符与关键字 下图为Java中所有的关键字 所有标识符必须以大小写字母或$或_开头,首字母之后可以用数字 不 ...
- Spring源码解析之ConfigurationClassPostProcessor(二)
上一个章节,笔者向大家介绍了spring是如何来过滤配置类的,下面我们来看看在过滤出配置类后,spring是如何来解析配置类的.首先过滤出来的配置类会存放在configCandidates列表, 在代 ...
- Linux学习手册
入门概述 Linux 简介 Linux 内核最初只是由芬兰人林纳斯·托瓦兹(Linus Torvalds)在赫尔辛基大学上学时出于个人爱好而编写的. Linux 是一套免费使用和自由传播的类 Unix ...