一、内核模块简单框架
1、test.c
// 内核模块包含的头文件 #include <linux/module.h> #include <linux/init.h> // 模块说明 MODULE_LICENSE("GPL"); MODULE_AUTHOR("Awendemo"); MODULE_DESCRIPTION("Test module"); MODULE_ALIAS("A simplest module"); // 模块初始化函数 static int __init test_init(void) { printk(KERN_INFO "Init!\n"); return 0; } // 模块清理函数 static void __exit test_exit(void) { printk(KERN_INFO "Exit!\n"); } // 注册函数 module_init(test_init); module_exit(test_exit);
2、对应的makefile
ifneq ($(KERNELRELEASE),) #模块名称与主文件test.o保持一致 MODULE_NAME := test #模块主文件test.c CORE_OBJS := test.o obj-m := $(CORE_OBJS) else PWD := $(shell pwd) KDIR := /usr/src/linux-headers-3.13.0-32-generic all: $(MAKE) -C $(KDIR) M=$(PWD) clean: rm -rf *.mod.c *.mod.o *.ko *.o *.tmp_versions *.order *symvers endif
3、将makefile和test.c放到同一目录,编译源码,生成内核模块test.ko
4、tail -f /var/log/kern.log 查看内核输入的日志信息
5、insmod test.ko 加载内核,rmmod test.ko 卸载内核,执行结果如下图:
二、内核模块添加数据包过滤功能
1、根据Netfilter框架文档,添加钩子函数
// 内核模块包含头文件 #include <linux/init.h> #include <linux/module.h> // netfilter框架包含头文件 #include <linux/netfilter.h> #include <linux/socket.h> #include <linux/netfilter_ipv4.h> #include <linux/skbuff.h> #include <linux/netdevice.h> #include <linux/inet.h> #include <net/ip.h> #include <net/tcp.h> // 模块说明 MODULE_LICENSE("GPL"); MODULE_AUTHOR("Awendemo"); MODULE_DESCRIPTION("Netfilter module"); MODULE_ALIAS("A simplest module"); // 记录钩子函数的结构体 static struct nf_hook_ops nfhoin; static struct nf_hook_ops nfhoout; // 网络包接受截获函数 unsigned int filter_in(unsigned int hooknum, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { struct iphdr *iph = NULL; struct tcphdr *tcph = NULL; struct udphdr *udph = NULL; if(skb == NULL) return NF_ACCEPT; iph = ip_hdr(skb); if(iph == NULL) return NF_ACCEPT; switch(iph->protocol) { case IPPROTO_TCP: { // 获取tcp头 tcph = (struct tcphdr *)(skb->data + (iph->ihl * 4)); if(tcph == NULL) return NF_ACCEPT; printk(KERN_INFO "TCP In Port [%u]-->[%u]\n", ntohs(tcph->source), ntohs(tcph->dest)); } break; case IPPROTO_UDP: { // 获取udp头 udph = (struct udphdr *)(skb->data + (iph->ihl * 4)); if(udph == NULL) return NF_ACCEPT; printk(KERN_INFO "UDP In Port [%u]-->[%u]\n", ntohs(udph->source), ntohs(udph->dest)); } break; case IPPROTO_ICMP: {} break; } return NF_ACCEPT; } // 网络包发送截获函数 unsigned int filter_out(unsigned int hooknum, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { struct iphdr *iph = NULL; struct tcphdr *tcph = NULL; struct udphdr *udph = NULL; if(skb == NULL) return NF_ACCEPT; iph = ip_hdr(skb); if(iph == NULL) return NF_ACCEPT; switch(iph->protocol) { case IPPROTO_TCP: { tcph = (struct tcphdr *)(skb->data + (iph->ihl * 4)); if(tcph == NULL) return NF_ACCEPT; printk(KERN_INFO "TCP Out Port [%u]-->[%u]\n", ntohs(tcph->source), ntohs(tcph->dest)); } break; case IPPROTO_UDP: { udph = (struct udphdr *)(skb->data + (iph->ihl * 4)); if(udph == NULL) return NF_ACCEPT; printk(KERN_INFO "UDP Out Port [%u]-->[%u]\n", ntohs(udph->source), ntohs(udph->dest)); } break; case IPPROTO_ICMP: {} break; } return NF_ACCEPT; } // 模块初始化函数 static int __init filter_init(void) { printk(KERN_INFO "filter_init\n"); // 注册钩子函数 nfhoin.hook = filter_in; nfhoin.hooknum = NF_INET_LOCAL_IN; nfhoin.pf = PF_INET; nfhoin.priority = NF_INET_LOCAL_IN; if(nf_register_hook(&nfhoin) < 0) { printk(KERN_INFO "nf_register_hook nfhoin failed!\n"); } nfhoout.hook = filter_out; nfhoout.hooknum = NF_INET_LOCAL_OUT; nfhoout.pf = PF_INET; nfhoout.priority = NF_INET_LOCAL_OUT; if(nf_register_hook(&nfhoout) < 0) { printk(KERN_INFO "nf_register_hook nfhoout failed!\n"); } return 0; } // 模块清理函数 static void __exit filter_exit(void) { // 注销钩子函数 nf_unregister_hook(&nfhoin); nf_unregister_hook(&nfhoout); printk(KERN_INFO "filter_exit\n"); } // 注册函数 module_init(filter_init); module_exit(filter_exit);
其中:
NF_IP_LOCAL_IN和NF_IP_LOCAL_OUT:简单的讲就是表示对接受和发送数据包的拦截,根据钩子函数的返回状态来处理这个数据包,防火墙一般建立在这两个HOOK上。filter_in和filter_out就是对应的钩子函数,他们的返回值意义如下:
NF_DROP 丢弃该数据包
NF_ACCEPT 保留该数据包
NF_STOLEN 忘掉该数据包
NF_QUEUE 将该数据包插入队列,通常交给用户处理
NF_REPEAT 再次调用该hook函数
2、编译模块加载模块信息,log信息如下图:
三、内核模块的一些说明
1、makefile
在如上makefile中,需要编译的C文件添加到“INCLUE_OBJS :=” 后面。同时,为了解决内核不匹配的问题,uname –r察看当前运行的内核版本,在“KDIR :=”后添加对应的路径。
ifneq ($(KERNELRELEASE),) #模块名称与主文件test.o保持一致 MODULE_NAME := test #模块主文件test.c CORE_OBJS := test.o #添加包含的C文件 file1.o file2.o INCLUE_OBJS := file1.o file2.o $(MODULE_NAME)-objs := $(CORE_OBJS) $(INCLUE_OBJS) obj-m := $(CORE_OBJS) else PWD := $(shell pwd) KDIR := /usr/src/linux-headers-3.13.0-32-generic all: $(MAKE) -C $(KDIR) M=$(PWD) clean: rm -rf *.mod.c *.mod.o *.ko *.o *.tmp_versions *.order *symvers endif
2、操作
加载:insmod(insmod xxx.ko)
卸载:rmmod (rmmod xxx)
查看:lsmod
3、许可声明
MODULE_LICENSE("GPL"); (必须有,否则insmod驱动时将不能与/proc/kallsyms中的符号正常连接)
MODULE_DESCRIPTION("xx");模块描述(可选)
MODULE_VERSION("xx");模块版本(可选)
MODULE_ALIAS("xx");模块别名(可选)
4、内核打印
printfk对应8个优先级:
KERN_EMERG "<0>"
KERN_ALERT "<1>"
KERN_CRIT "<2>"
KERN_ERR "<3>"
KERN_WARNING "<4>"
KERN_NOTICE "<5>"
KERN_INFO "<6>"
KERN_DEBUG "<7>"
优先级越高数字越小,如果没有设优先级,那默认优先级是4。
/proc/sys/kernel/printk,该文件可以调节printk的输出等级,文件中有四个数字值,分别为:
控制台日志级别:优先级高于该值的消息将被打印至控制台;
默认的消息日志级别:用该优先级来打印未定义优先级的消息;
最低的控制台日志级别:控制台日志界别可被设置的最小值;
默认的控制台日志级别:控制台日志级别的默认值。
printfk输出可使用shell命令dmesg查看,任何级别的打印信息都可以在 /var/log/kern.log中查看,对于不同的linux版本可能有所不同,我是使用的ubuntu 14.04 LTS。
信息不是很全面,希望大家多多纠正!