Linux内核编程 -- 从HelloWord到基于NetFilter的Linux驱动Demo
来源:互联网 发布:数控冲床编程怎么学 编辑:程序博客网 时间:2024/05/21 11:02
基于Linux Ubuntu
1.安装内核头文件
1.1查看Linux内核版本
usname -r
1.2安装Linux内核头文件
sudo apt-get install linux-headers-`uname -r`
默认安装目录:/lib/modules/"内核版本号"
2.编写HelloWord
2.1 编写hello.c
随便进入一个目录,
使用:touch hello.c
新建一个hello.c文件。
使用:vim hello.c
编辑新建好的hello.c文件的内容,如下:
#include <linux/init.h>#include <linux/module.h>#include <linux/kernel.h>/* 非必需内容*/MODULE_LICENSE("Dual BSD/GPL");//许可证MODULE_AUTHOR("xushuzhan");//作者MODULE_DESCRIPTION("hello demo");//模块描述MODULE_VERSION("1.0");///* 入口函数 */static int hello_init(void){ printk(KERN_ALERT "hello_init is called\n");}/* 退出函数 */static void hello_exit(void){ printk(KERN_ALERT "hello_exit is called\n");}/* 注册 */module_init(hello_init);module_exit(hello_exit);
可以看到,自己写的入口函数和出口函数都通过最后两个函数完成了注册,因此,在安装和卸载模块的时候,会分别调用这两个函数,并输出相应的内容到日志文件中,用dmesg命令即可查看。
2.2 编写Makefile
使用:touch Makefile
创建Makefile文件(Makefile文件主要用来定义编译的规则,创建的时候要注意文件名大小写。):
obj-m := hello.odefault: make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
obj-m
的意思是将目标文件编译成模块。 $(shell uname -r)
代表内核的版本号。 $(shell pwd)
代表当前目录。
2.3 编译模块
sudo make
编译成功即可看到当前目录多了一个hello.ko文件,这就是之后需要安装到内核的模块。
2.4 安装模块到内核
sudo insmod hello.ko
使用这条命令便可以把模块安装到内核中去,此时会调用刚刚已经注册的函数,将“hello_init is called”写入日志文件。
2.5 从内核删除模块
sudo rmmod hello.ko
使用这条命令便可以把模块从内核删除,此时会调用刚刚已经注册的函数,将“hello_exit is called”写入日志文件。
2.6 查看日志内容
可以看到在在安装和删除过程中打印出来的内容:
3.认识Netfilter
3.1 能干嘛
NetFilter是一个Linux下的包过滤防火墙,集成在内核中,设立了5个Hook点,用于拦截在Linux中流通的数据包,其中五个Hook点的位置如下:
NF_INET_PRE_ROUTING:刚刚进入网络层的数据包通过此点(刚刚进行完版本号,校验和等检测),目的地址转换在此点进行;
NF_INET_LOCAL_IN:经路由查找后,送往本机的通过此检查点,INPUT包过滤在此点进行;
NF_INET_FORWARD:要转发的包通过此检测点,FORWARD包过滤在此点进行;
NF_INET_POST_ROUTING:所有马上便要通过网络设备出去的包通过此检测点,内置的源地址转换功能(包括地址伪装)在此点进行;
NF_INET_LOCAL_OUT:本机进程发出的包通过此检测点,OUTPUT包过滤在此点进行。
数据包的流通过程如下:
3.2 钩子函数结构与返回值
在使用之前要特别注意网上某些教程里面的钩子函数是这样定义的:
unsigned int hook_func(unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)){}
实际上这是在老版本内核上的写法,为此,我在实际操作的时候卡了一天,老师编译不出。
新版内核(如4.10.0_XX)的钩子函数定义应该是这样的:
static unsigned int watch_in(void *priv, struct sk_buff *skb, const struct nf_hook_state *state){}
钩子函数又几个返回值,他们的意义如下:
NF_DROP 丢弃该数据包 NF_ACCEPT 保留该数据包 NF_STOLEN 忘掉该数据包 NF_QUEUE 将该数据包插入到用户空间 NF_REPEAT 再次调用该hook函数
对于优先级,有如下定义,只是也写枚举的常量:
NF_IP_PRI_FIRST = INT_MIN, NF_IP_PRI_CONNTRACK_DEFRAG = -400, NF_IP_PRI_RAW = -300, NF_IP_PRI_SELINUX_FIRST = -225, NF_IP_PRI_CONNTRACK = -200, NF_IP_PRI_MANGLE = -150, NF_IP_PRI_NAT_DST = -100, NF_IP_PRI_FILTER = 0, NF_IP_PRI_SECURITY = 50, NF_IP_PRI_NAT_SRC = 100, NF_IP_PRI_SELINUX_LAST = 225, NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX, NF_IP_PRI_LAST = INT_MAX,
3.3 使用NetFilter实现对HTTP协议GET/POST的流量分类
最终的功能是将本机IP和目的IP以及本机发出的GEP和POST数据包分类输出到日志文件中,如下图所示。
步骤:
3.3.1 定义一个nf_hook_ops
结构体,并指定自己写的hook函数
//define a hook function struct nf_hook_ops filter_GET_POST_ops = { .list = {NULL,NULL}, .hook = filter_GET_POST, //hooked function .pf = NFPROTO_IPV4, //net protocol .hooknum = NF_INET_LOCAL_OUT, //hook point .priority = NF_IP_PRI_FILTER+2 //priority };
3.3.2 定义一个hook函数(我这里定义的是filter_GET_POST_ops
)
static unsigned int filter_GET_POST(void *p, struct sk_buff *skb, const struct nf_hook_state *s){ __be32 sip,dip; if(skb){ char *data = NULL; //tcp data char *request_method;//http request method id request line struct sk_buff *sb = NULL; struct tcphdr *tcph = NULL; sb = skb; struct iphdr *iph; iph = ip_hdr(sb); tcph = tcp_hdr(sb); sip = iph->saddr; //source ip address dip = iph->daddr; //destination ip address //(64_bit pc) get tcp's data pointor data = (unsigned long)tcph + (unsigned long)(tcph -> doff * 4); if(request_method = strstr(data,"GET")!=NULL){ printk(">>GET>>>>GET>>>>>GET>>>>>>>>>GET>>>>>>>>>>>>>GET>>>>>>>>>>>> \n"); printk("[GET] Packet from: %d.%d.%d.%d to %d.%d.%d.%d ", NIPQUAD(sip), NIPQUAD(dip)); printk("GET -> %s \n",data); }else if(request_method = strstr(data,"POST")!=NULL){ printk(">>POST>>>>POST>>>>>POST>>>>>>>>>POST>>>>>>>>>>>>>POST>>>>>>>>>>>> \n"); printk("[POST] Packet from: %d.%d.%d.%d to %d.%d.%d.%d", NIPQUAD(sip), NIPQUAD(dip)); printk("POST -> %s \n",data); } } return NF_ACCEPT; }
整体的思路很简单:
1.从sk_buff解析出IP包,拿到收不,便可以解析出目的IP和源IP。
2.从IP包中解析出TCP包的data部分,便可以拿到拿到请求行、请求头、请求体,请求方法是位于请求行中的,使用一个strstr()
函数匹配出即可。
3.3.3 注册装/卸载函数
static int __init filter_GET_POST_init(void) { nf_register_hook(&filter_GET_POST_ops); return 0; } static void __exit filter_GET_POST_exit(void) { nf_unregister_hook(&filter_GET_POST_ops); } module_init(filter_GET_POST_init); module_exit(filter_GET_POST_exit);
至此,一个简单的domo,就做出来了,将其编译安装到内核中以后,使用一些在线测试API的网站,模拟一些请求,便可以在日志文件中看到不同的打印数据了。
4.完整代码
net_hook.c
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/types.h> #include <linux/netdevice.h> #include <linux/skbuff.h> #include <linux/netfilter_ipv4.h> #include <linux/inet.h> #include <linux/in.h> #include <linux/ip.h> #include <linux/tcp.h> #include <linux/udp.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("XuShuzhan");MODULE_DESCRIPTION("A NetFilter Demo");MODULE_VERSION("1.0"); #define NIPQUAD(addr) \ ((unsigned char *)&addr)[0], \ ((unsigned char *)&addr)[1], \ ((unsigned char *)&addr)[2], \ ((unsigned char *)&addr)[3] static unsigned int filter_GET_POST(void *p, struct sk_buff *skb, const struct nf_hook_state *s){ __be32 sip,dip; if(skb){ char *data = NULL; //tcp data char *request_method;//http request method id request line struct sk_buff *sb = NULL; struct tcphdr *tcph = NULL; sb = skb; struct iphdr *iph; iph = ip_hdr(sb); tcph = tcp_hdr(sb); sip = iph->saddr; //source ip address dip = iph->daddr; //destination ip address //(64_bit pc) get tcp's data pointor data = (unsigned long)tcph + (unsigned long)(tcph -> doff * 4); if(request_method = strstr(data,"GET")!=NULL){ printk(">>GET>>>>GET>>>>>GET>>>>>>>>>GET>>>>>>>>>>>>>GET>>>>>>>>>>>> \n"); printk("[GET] Packet from: %d.%d.%d.%d to %d.%d.%d.%d ", NIPQUAD(sip), NIPQUAD(dip)); printk("GET -> %s \n",data); }else if(request_method = strstr(data,"POST")!=NULL){ printk(">>POST>>>>POST>>>>>POST>>>>>>>>>POST>>>>>>>>>>>>>POST>>>>>>>>>>>> \n"); printk("[POST] Packet from: %d.%d.%d.%d to %d.%d.%d.%d", NIPQUAD(sip), NIPQUAD(dip)); printk("POST -> %s \n",data); } } return NF_ACCEPT; } //regist a hook function struct nf_hook_ops filter_GET_POST_ops = { .list = {NULL,NULL}, .hook = filter_GET_POST, //hooked function .pf = NFPROTO_IPV4, .hooknum = NF_INET_LOCAL_OUT, .priority = NF_IP_PRI_FILTER+2 }; static int __init filter_GET_POST_init(void) { nf_register_hook(&filter_GET_POST_ops); return 0; } static void __exit filter_GET_POST_exit(void) { nf_unregister_hook(&filter_GET_POST_ops); } module_init(filter_GET_POST_init); module_exit(filter_GET_POST_exit);
Makefile
obj-m := net_hook.odefault: make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
- Linux内核编程 -- 从HelloWord到基于NetFilter的Linux驱动Demo
- LVS基于linux内核的netfilter机制
- linux内核驱动之helloWord本地驱动
- linux内核编程demo
- 【Linux内核层】深入netfilter编程
- linux驱动设计----helloword
- 从linux内核中学到的编程技巧
- 从linux内核中学到的编程技巧
- 从linux内核中学到的编程技巧
- 从linux内核中学到的编程技巧
- 【Linux 驱动】Netfilter/iptables (六) 内核协议栈编程(发送skb)
- linux 内核netfilter实现
- 基于Linux内核的input子系统驱动
- 基于Linux内核的IIC驱动
- Linux内核--基于Netfilter的内核级包过滤防火墙实现
- Linux内核--基于Netfilter的内核级包过滤防火墙实现
- Linux内核--基于Netfilter的内核级包过滤防火墙实现
- Linux内核--基于Netfilter的内核级包过滤防火墙实现
- 【理解】汉诺塔问题,新手看这里
- zk的jar包冲突:java.lang.NoSuchMethodError: org.apache.zookeeper.ZooKeeper.getChildren(Ljava/lang/String;
- 读MNIST源码(二):tensorflow基础
- oracle ebs
- 猫狗大战遇到问题
- Linux内核编程 -- 从HelloWord到基于NetFilter的Linux驱动Demo
- 如何关闭Golang中的HTTP连接 How to Close Golang's HTTP connection
- ORA-39365 Error Reported by DataPump Import (IMPDP) When SUPPLEMENTAL_LOG_DATA_MIN Is Set To YES (文档
- WLAN连不上网和以太网连不上网的解决办法
- 【MIUI8_7.6.10】红米NOTE3 全网通 KENZO 高通骁龙650 基于安卓M(Android 6.0)修改精简优化版本
- ubuntu下实用技巧
- JAVA Runnable接口
- JavaSE中的变量
- PDF.js实现页面初始化比例最优配置