作用:编写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