linux内核驱动编写,读取网络数据包,加载到设备文件

作用:编写linux内核驱动程序,驱动程序读取网络数据包,对数据包进行解析,将tcp五元组加载到设备文件中,对设备文件数据进行读取写入到临时文件。

 

读取tcp数据包源码:

//print_tcp.c

#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/icmp.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/uaccess.h>

//分解IP源地址、目的地址的宏
#define NIPQUAD(addr) \
((unsigned char *)&addr)[0], \
((unsigned char *)&addr)[1], \
((unsigned char *)&addr)[2], \
((unsigned char *)&addr)[3]

//设备文件大小,用于存储读取的网络数据包
#define DEVICE_BUF 102400
#define DATA_BUF 1024
#define LOG_BUF 2048
#define TMP_BUF 2

static struct nf_hook_ops nfho;

static int major = 0;
static int minor = 0;

/*device data*/
static struct cdev cdev;
struct class *dev_class = NULL;

char data_buf[DEVICE_BUF] = "";
char log_file[LOG_BUF] = "";
char tcp_data[DATA_BUF] = "";

//内核态与用户态交互函数,用户态read时触发,将data_buf数据写入到设备文件中
ssize_t hello_dev_read(struct file* file, char __user* buf, size_t count, loff_t* offset)
{
    //copy_to_user函数将内核态数据写入到用户态
    if(!copy_to_user((char*)buf, data_buf, strlen(data_buf)))
    {
        memset(data_buf, '\0', sizeof(data_buf));
        return 0;
    }
    else
    {
        memset(data_buf, '\0', sizeof(data_buf));
        return -1;
    }
    //printk("data_buf:%s\n", data_buf);

}

//用户态调用close时触发
int static hello_dev_release(struct inode *inode, struct file *file)
{
    //printk("file release in hello_dev_release......finished!\n");
    return 0;
}

//用户态调用open时触发
int static hello_dev_open(struct inode *inode, struct file *file)
{
    //printk("file release in hello_dev_release......finished!\n");
    return 0;
}

//内核驱动挂载函数
//读取网络数据包
static unsigned int ptcp_hook_func(const struct nf_hook_ops *ops,
                                   struct sk_buff *skb,
                                   const struct net_device *in,
                                   const struct net_device *out,
                                   int (*okfn)(struct sk_buff *))
{
    struct iphdr *iph;          /* IPv4 header */
    struct tcphdr *tcph;        /* tcp header */
    unsigned char *user_data;   /* TCP data begin pointer */
    unsigned char *tail;        /* TCP data end pointer */
    unsigned char *it;          /* TCP data iterator */
    char tmp[TMP_BUF] = "";

    memset(tcp_data, '\0', sizeof(tcp_data)); 
    memset(log_file, '\0', sizeof(log_file)); 
    if (!skb)
        return NF_ACCEPT;

    iph = ip_hdr(skb);          /* get IP header */

    if (iph->protocol != IPPROTO_TCP)
        return NF_ACCEPT;

    tcph = tcp_hdr(skb);        /* get TCP header */
    
    //user_data为tcp数据包数据部分
    user_data = (unsigned char *)((unsigned char *)tcph + (tcph->doff * 4));
    tail = skb_tail_pointer(skb);

    /* Print TCP packet data (payload) */
    for (it = user_data; it != tail; ++it) 
    {
        char c = *(char *)it;

        if (c == '\0')
            break;

        //printk("%c", c);
        memset(tmp, '\0', sizeof(tmp));
        sprintf(tmp, "%c", c);
        if(strlen(tcp_data) < DATA_BUF)
        {
            strcat(tcp_data, tmp);
        }
    }

    //sprintf(log_file, "protocol_type:%d, src address:%u.%u.%u.%u,dst address:%u.%u.%u.%u, src_port:%d, dst_port:%d, data:%s\n",
     //       iph->protocol, NIPQUAD(iph->saddr), NIPQUAD(iph->daddr), ntohs(tcph->source), ntohs(tcph->dest), tcp_data);

    sprintf(log_file, "protocol_type:%d, src address:%u.%u.%u.%u,dst address:%u.%u.%u.%u, src_port:%d, dst_port:%d\n",
            iph->protocol, NIPQUAD(iph->saddr), NIPQUAD(iph->daddr), ntohs(tcph->source), ntohs(tcph->dest));
    printk("data:%s\n", log_file);
    if(strlen(data_buf) < DEVICE_BUF)
    { 
        strcat(data_buf, log_file);
    }
    return NF_ACCEPT;
}

//设备文件定义,用于内核态与用户态数据交互
static struct file_operations fops = {
        .owner = THIS_MODULE,
        .open = hello_dev_open,
        .release = hello_dev_release,
        .read = hello_dev_read,
        //.write = hello_dev_write,
};

//内核驱动初始化
//创建用于数据包写入的设备文件
static int __init ptcp_init(void)
{
    int ret;
    dev_t devno;
    dev_t dev = 0; 

    int res;
    printk("ptcp_init...\n");

    nfho.hook = (nf_hookfn *)ptcp_hook_func;    /* hook function */
    nfho.hooknum = NF_INET_PRE_ROUTING;         /* received packets */
    nfho.pf = PF_INET;                          /* IPv4 */
    nfho.priority = NF_IP_PRI_FIRST;            /* max hook priority */

    res = nf_register_hook(&nfho);
    if (res < 0) {
        pr_err("print_tcp: error in nf_register_hook()\n");
        return res;
    }

    ret = alloc_chrdev_region(&dev, minor, 1, "hello_dev");
    if (ret < 0)
    {
        printk("register_chrdev_region failed!\n");
        return ret;
    }
    major = MAJOR(dev);
    minor = MINOR(dev);
    printk("major = %d, minor = %d\n", major, minor);

    devno = MKDEV(major, minor);  
    cdev_init(&cdev, &fops); 
    cdev_add(&cdev, devno, 1);

    dev_class = class_create(THIS_MODULE, "dev_class");
    if(IS_ERR(dev_class)){
        printk("class_create failed!\n");
        return -1;
    }

    device_create(dev_class, NULL, devno, NULL, "hello");

    return 0;
}

//驱动文件退出
//卸载设备文件
static void __exit ptcp_exit(void)
{
    dev_t devno;
    printk("ptcp_exit...\n");
    nf_unregister_hook(&nfho);

    devno = MKDEV(major, minor);

    cdev_del(&cdev);

    device_destroy(dev_class, devno);
    class_destroy(dev_class);

    unregister_chrdev_region(devno, 1);
    return;
}

module_init(ptcp_init);
module_exit(ptcp_exit);

MODULE_LICENSE("GPL");
//Makefile文件

ifneq ($(KERNELRELEASE),)
obj-m:=print_tcp.o
#obj-m:=hello.o
#obj-m:=print_udp.o
else
PWD:=$(shell pwd)
KDIR:=/usr/lib/modules/$(shell uname -r)/build
all:
	$(MAKE) -C $(KDIR) M=$(PWD)
clean:
	rm -rf *.o *.mod.c *.ko *.symvers *.order *.markers
endif

 执行命令:

1、make clean; make 

2、生成print_tcp.ko驱动文件

3、加载驱动

4、卸载驱动

 

数据包从驱动内核态读取到设备文件代码:

//main.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>

//设备文件名称
#define DEVNAME "/dev/hello"
//读取的内核态数据包大小
#define DEVICE_BUF 102400

int main()
{
    char buf[DEVICE_BUF] = "";
    int fd;
   
    while(1)
    {
        //读取设备文件写入到临时文件
        FILE *file = fopen("/root/tmp/aaa.txt", "a+");
        if(file == NULL)
        {
            return -1;    
        }

        //打开设备文件,调用内核驱动中的hello_dev_open函数
        fd = open(DEVNAME, O_RDWR);
        if(fd == -1)
        {
              printf("file %s is opening......failure!", DEVNAME);
              sleep(10);
        }
        //读取设备文件数据,调用内核驱动中的hello_dev_read
        read(fd, buf, DEVICE_BUF);
        //关闭设备文件,调用内核驱动中的hello_dev_release
        close(fd);

        fwrite(buf, strlen(buf), 1, file);
        //printf("%s\n", buf);

        memset(buf, '\0', sizeof(buf));
        //由于网络数据包大小未知,将循环时间设置成0.1s,尽量不遗漏进来的数据包
        usleep(100000);
        fclose(file);
    }

    return 0;
}

1、编译:gcc -o main main.c

2、执行:./main

3、查看临时文件/root/tmp/aaa.txt

可以看到读取的tcp数据包信息。

 

备注:

1、ptcp_init函数用于初始化内核驱动程序。

ptcp_exit函数用于驱动程序卸载时的清理操作。

ptcp_hook_func函数为捕获TCP数据包挂载函数,挂载点为NF_INET_PRE_ROUTING(可对挂载点进行修改,捕获对应挂载点的数据包)。

module_init, module_exit, MODULE_LICENSE函数为内核驱动程序固定格式。

参考:https://codeday.me/bug/20190325/825084.html

https://blog.csdn.net/xiadeliang1111/article/details/103522397

 

2、struct file_operations:将系统调用与内核驱动程序关联起来的结构体。

结构体的每一个成员都对应着一个系统调用,比如.read=hello_dev_read,用户态使用系统调用read函数读取指定设备文件时,即会调用内核驱动中hello_dev_read函数。

参考:https://blog.csdn.net/yusiguyuan/article/details/11352155

 

3、

alloc_chrdev_region函数为动态分配设备文件设备编号。

MAJOR函数为获取设备文件主设备号。

MINOR函数为获取设备文件次设备号。

MKDEV函数为将主设备号和次设备号转换成dev_t类型的函数。

cdev_init函数为初始化cdev结构体。

cdev_add函数为添加设备到系统中。

class_create函数为创建设备类。

device_create函数为创建设备文件。

 

4、

cdev_del函数为释放cdev结构。

device_destroy函数为卸载设备文件。

class_destroy函数为卸载设备类。

unregister_chrdev_region函数为释放设备号。

 

5、

hello_dev_read()函数为内核态与用户态交互函数,当用户态调用read读取设备模块时,会调用此函数。

hello_dev_open()函数为内核态与用户态交互函数,当用户调用open读取设备模块时,会调用此函数。

hello_dev_release ()函数为内核态与用户态交互函数,当用户调用close读取设备模块时,会调用此函数。

hello_dev_read函数中copy_to_user函数作用是将内核态的数据加载到用户态的设备文件中,用于用户态与内核态的数据交互。

参考:http://blog.sina.com.cn/s/blog_69ef92d60100l8ac.html

 

6、

此外,如果是修改内核捕获数据包,需要在nf_conntrack_core.c文件nf_conntrack_in函数中进行修改。此函数主要是对进来出去的的数据包进行修改。

参考:https://blog.csdn.net/City_of_skey/article/details/84934016

 

研发过程中遇到的问题:

1、之前编写内核驱动程序读取网络数据包之后,想通过写文件的方式将数据包写入到用户态文件中,发现加入写文件功能后经常出现系统崩溃的情况。于是,就没有采用写文件的方式。

linux内核驱动写文件参考:https://www.cnblogs.com/pengdonglin137/articles/6216435.html

 

如果报错:找不到kernel:

如:/usr/lib/modules/3.10.0-514.26.2.el7.x86_64/build: No such file or directory.  Stop

https://buildlogs.centos.org/c7.1611.u/kernel/20170704132018/3.10.0-514.26.2.el7.x86_64/

去上面的网站下载对应的rpm包,安装到/usr/src/kernels路径下,再进行编译。

 

参考:

tcphdr结构:

https://www.cnblogs.com/chengliangsheng/archive/2014/03/22/3598883.html

 

centos编译内核:

https://www.jb51.net/article/100954.htm

 

skb_buff结构:

https://blog.csdn.net/YuZhiHui_No1/article/details/38666589

 

netfilter链接跟踪实现之nf_conntrack_in函数:

https://blog.csdn.net/City_of_skey/article/details/84934016

 

IP协议中8位协议号,指明上层协议:

https://www.cnblogs.com/classics/p/10417402.html

 

linux内核驱动读写文件:

https://www.cnblogs.com/pengdonglin137/articles/6216435.html

 

linux内核驱动打印TCP数据包:

https://codeday.me/bug/20190325/825084.html

内核设备文件编写:

http://blog.sina.com.cn/s/blog_69ef92d60100l8ab.html

 

linux内核驱动内核态与用户态数据交互:

https://blog.csdn.net/xiaodingqq/article/details/80150347

http://blog.sina.com.cn/s/blog_69ef92d60100l8ac.html

 

创建设备驱动文件:

https://blog.csdn.net/qq_30624591/article/details/85339304

 

 

 

 

 

 

 

 

 

おすすめ

転載: blog.csdn.net/xiadeliang1111/article/details/103627447