作用:編寫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