继前面介绍了 netfilter hook,这里我们开始进行简单的实例讲解,主要是Netfilter hook的注册与注销:

wqlkp.c:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>

MODULE_LICENSE("Dual BSD/GPL");

static struct nf_hook_ops nfho;

//钩子函数,注意参数格式与开发环境源码树保持一致
unsigned int 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 *))
{
    return NF_DROP;//丢弃所有的数据包
}

static int __init hook_init(void)
{
    nfho.hook = hook_func;//关联对应处理函数
    nfho.hooknum = NF_INET_PRE_ROUTING;//2.6.22及以后的内核中,内核态编程需要使用这个
    nfho.pf = PF_INET;//ipv4,所以用这个
    nfho.priority = NF_IP_PRI_FIRST;//优先级,第一顺位,首先执行我们定义的函数

    nf_register_hook(&nfho);//注册

    return 0;
}
static void __exit hook_exit(void)
{
    nf_unregister_hook(&nfho);//注销
}

module_init(hook_init);
module_exit(hook_exit);

上面涉及到的数据结构及函数接口,请见上一篇博文。

Makefile:

ifneq ($(KERNELRELEASE),)
    obj-m := wqlkp.o
else
    KERNELDIR := /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
all:
    $(MAKE) -C $(KERNELDIR) SUBDIRS=$(PWD) modules
endif
clean:
    rm -f *.o *.ko *.mod.c .wqlkp*

加载模块后的结果就是,你访问不了网络了,要解除的话,卸载这个模块就行了。

为啥会出现上面的结果?主要看这两个

nfho.hook = hook_func;//关联的这个函数直接返回NF_DROP(看前篇博文),丢弃所有数据包,不再传输
nfho.hooknum = NF_INET_PRE_ROUTING;//这个告诉我们处理(关卡)的地点在哪,
           //结合上面的处理函数,就是数据包刚通过链路层解包,进入网络层之后,它就被“关卡拦截”丢弃了,
           //不再在协议栈中传输了,上层的传输层应用层均收不到数据包。

ok,老规矩,知其然还要知其所以然,我们去窥探下内核源码(Linux kernel 3.14):

注册函数:int nf_register_hook(struct nf_hook_ops *reg)

int nf_register_hook(struct nf_hook_ops *reg)
{
    struct nf_hook_ops *elem;
    int err;

//互斥锁加锁(可中断)
    err = mutex_lock_interruptible(&nf_hook_mutex);
    if (err < 0)
        return err;

//内核链表中的一个函数,常用于遍历链表搜索元素
//这里是插入新对象,下面的循环遍历就是根据优先级找到合适的插入位置,这样在进行规则匹配的时候
//就是按照优先级来的
    list_for_each_entry(elem, &nf_hooks[reg->pf][reg->hooknum], list) {
        if (reg->priority < elem->priority)
            break;
    }
    list_add_rcu(&reg->list, elem->list.prev);//插入新对象(参数指定)
    mutex_unlock(&nf_hook_mutex);//释放锁
#if defined(CONFIG_JUMP_LABEL)
    static_key_slow_inc(&nf_hooks_needed[reg->pf][reg->hooknum]);//忽略...
#endif
    return 0;
}

从上面的代码就可以看出,所有的 struct nf_hook_ops 都以指针的形式记录在一个列表中,其保存的顺序就是按照优先级的顺序。这个表是一个二维数组,用来存储不同协议过滤点的回调处理函数。通过协议族类型和hook类型定位。如下图所示:

extern struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];

这里写图片描述

注销函数:void nf_unregister_hook(struct nf_hook_ops *reg)

void nf_unregister_hook(struct nf_hook_ops *reg)
{
    mutex_lock(&nf_hook_mutex);//获取互斥锁,不可中断
    list_del_rcu(&reg->list);//删除指定对象
    mutex_unlock(&nf_hook_mutex);//释放锁
#if defined(CONFIG_JUMP_LABEL)
    static_key_slow_dec(&nf_hooks_needed[reg->pf][reg->hooknum]);
#endif
    /**
     *  synchronize_net -  Synchronize with packet receive processing
     *
     *  Wait for packets currently being received to be done.
     *  Does not block later packets from starting.
     */
    synchronize_net();
}

注销一个 Netfilter hook 其实就是把我们之前注册插入的 struct nf_hook_ops 指针对象从链表中删除,从代码可以看出,它并没有销毁这个对象。
在后面还有个 synchronize_net(); 这个函数可能会引起睡眠,其目的是等待处理完数据包接收过程。

参考资料:
《深入Linux网络核心堆栈》
Linux kernel 3.14 sourcecode

Logo

更多推荐