Linux内核模块简单示例

一、内核模块简单框架

  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信息如下图:

         Linux内核模块简单示例_第1张图片

三、内核模块的一些说明

  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。

         信息不是很全面,希望大家多多纠正!


你可能感兴趣的:(Linux内核模块简单示例)