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

 

參考:

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

 

 

 

 

 

 

 

 

 

發佈了83 篇原創文章 · 獲贊 19 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章