Linux3.13.0 netfilter 学习笔记 之一 HOOK机制

netfilter介绍

Linux netfilter就是借助一整套的 hook 函数的管理机制,实现数据包在三层以上的过滤、地址转换(SNAT、DNAT)、基于协议的连接跟踪。我们所说的内核的netfilter,应该包括二层数据的filter操作,以及对三层及三层以上数据的filter等操作。
只不过二层的filter实现与三层及三层之上的filter实现有所不同。其中二层的filter与应用层程序ebtables结合使用,而三层及以上的filter结合iptables使用。但是二层filter与三层filter使用的都是统一的hook机制。

整体的hook机制及工作流程

linux抽象出整体的hook架构,通过在以下几个数据流经点添加hook机制,为实现netfilter提供基础框架:
NF_IP_PRE_ROUTING、NF_IP_LOCAL_IN、NF_IP_FORWARD、NF_IP_LOCAL_OUT、NF_IP_POST_ROUTING。
这5个点的含义如下:
NF_IP_PRE_ROUTING:刚刚进入网络层而没有进行路由之前的网络数据会通过此点(完成版本号、校验和等检查)。
NF_IP_FORWARD:在接收到的网络数据向另一个网卡进行转发之前通过此点。
NF_IP_POST_ROUTING:任何马上要通过网络设备出去的包通过该检测点,这是netfilter最后一个设置检测的点,内置的目的地址转换功能(包括地址伪装)在此点进行。
NF_IP_LOCAL_IN:在接收到的报文做路由,确定是本机接收的报文之后。
NF_IP_LOCAL_OUT:在本地报文做发送路由之前。
这五个点在数据的流经方向如下图:
在这里插入图片描述

整体的hook机制的函数调用过程

当物理网络上有网络数据到来时,ip_rcv()函数会接收到。此函数在最后会调用 NF_HOOK函数宏将控制权交给在PREROUTING点的处理规则处理。处理完毕后,则由函数ip_rev_finish()来查询路由表,判断此数据是发给本地还是转发给另一个网络。
如果此网络数据是发给本地的,就会调用ip_local_deliver()函数。该函数在最后调用宏 NF_HOOK,由netfilter的INPUT处理规则处理。INPUT处理完后交给传输层,传给应用层中的用户进程。
如果此数据是转发。则调用ip_route_input()函数后将控制权交给ip_forward()函数。ip_forward()函数在最后会调用NF_HOOK函数宏,由netfilter的FORWARD处理规则处理。处理完毕后调用ip_forward_finish()函数,由其中的ip_send()函数将数据发送。在发出此数据之前会通过NF_HOOK 函数宏,由netfilter 的 POSTROUTING处理规则处理,处理完毕后,将网络数据转给网络驱动程序,通过网络设备发送到物理网络上。
当本地机器要发送网络数据时,netfilter会在将数据交给规则POSTROUTING处理之前,由处理规则OUTPUT先进行处理。
函数调用流程图如下:
在这里插入图片描述

数据结构

nf_hook_ops

Hook点回调函数相关的数据结构,其是nf hook机制的重要数据结构,各成员的意义如下:

struct nf_hook_ops
{
	struct list_head list; //由结构nf_hook_ops构成一个链表,list是此链表的表头,把各个hook函数组成一个表。初始值为{NULL, NULL}
 	/* User fills in from here down. */
	nf_hookfn *hook;// 用户自定义的hook处理函数。
	struct module *owner;//模块所有者
	int pf;//协议族
	int hooknum;//hook点,取值为5个HOOK点之一。一个HOOK点可以挂多个HOOK函数,谁先被调用要看优先级。
	/* Hooks are ordered in ascending priority. */
	int priority;//优先级。取值越小,优先级越高。
};

nf_hookfn

nf_hookfn函数是用户注册的Hook回调函数的原型定义,其中的参数由netfilter自动传给Hook函数,其各个参数的含义如下:
hooknum:hook点
skb:数据包
in:数据包入口设备
out:数据包出口设备
okfn:是个函数指针,当回调函数为空时netfilter调用的处理函数
nf_hookfn函数原型定义如下:

typedef unsigned int nf_hookfn(unsigned int hooknum,
       struct sk_buff *skb,
       const struct net_device *in,
       const struct net_device *out,
       int (*okfn)(struct sk_buff *));

HOOK函数的返回值必须是NF_DROP、NF_ACCEPT、NF_STOLEN、NF_QUEUE、NF_REPEAT、NF_STOP之一。其含义如下:
NF_ACCEPT:继续传递,保持和原来传输的一致;
NF_DROP:丢弃包,不再继续传递;
NF_STOLEN:接管包,不再继续传递;
NF_QUEUE:队列化包(通常为用户空间处理做准备);
NF_REPEAT:再次调用该HOOK函数;
当HOOK函数的返回值为NF_ACCEPT时,在同一个HOOK点挂接的多个HOOK函数均可以执行。

nf_hooks

nf_hooks是一个二维数组,该数组中每一个元素是一个链表头元素,即每一个数组元素可以延伸出一个链表。链表中每个节点为一个nf_hook_ops结构,代表一个协议族在一个hook点上的hook函数。其中协议有32个,而hook点有8个,目前使用的是5个,分别为NF_IP_PRE_ROUTING、NF_IP_LOCAL_IN、NF_IP_FORWARD、NF_IP_LOCAL_OUT、NF_IP_POST_ROUTING. 而在同一个链表上,节点是按照优先级顺序排列的,优先级值最小的nf_hook_ops结构体存在链表的最前面,优先执行。
nf_hooks数组的定义如下:

struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS];

HOOK机制的注册、执行相关的函数

hook注册函数nf_register_hook

功能: 将一个新的nf_hook_ops结构添加到nf_hooks数组的相应的成员链表中
A) 根据协议族pf、hook点hooknum,从数组nf_hooks中索引得到相应的链表
B )根据优先级将该nf_hook_ops结构添加到链表的合适位置

// Linux 2.6.21 访问nf_hooks数组,需要添加自旋锁
int nf_register_hook(struct nf_hook_ops *reg)
{
    struct list_head *i;
    spin_lock_bh(&nf_hook_lock);
    list_for_each(i, &nf_hooks[reg->pf][reg->hooknum])
    {
        if (reg->priority < ((struct nf_hook_ops *)i)->priority)
            break;
    }
    list_add_rcu(->list, i->prev);
    spin_unlock_bh(&nf_hook_lock);
    synchronize_net();
    return 0;
}

// Linux 3.13.0 访问nf_hooks数组,需要添加互斥锁
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);	/*解开中断*/
	return 0;
}

Hook注销函数nf_unregister_hook

功能: 将一个nf_hook_ops结构从nf_hooks数组的相应的成员链表中删除
删除操作就是平常的删除链表成员的操作,比较简单

// Linux 2.6.21
void nf_unregister_hook(struct nf_hook_ops *reg)
{
	spin_lock_bh(&nf_hook_lock);
	list_del_rcu(&reg->list);
	spin_unlock_bh(&nf_hook_lock);
	synchronize_net();
}
// Linux 3.13.0 
void nf_unregister_hook(struct nf_hook_ops *reg)
{
	mutex_lock(&nf_hook_mutex);
	list_del_rcu(&reg->list);
	mutex_unlock(&nf_hook_mutex);
	synchronize_net();
	nf_queue_nf_hook_drop(reg);
}

hook 执行函数

目前调用执行hook回调函数的为宏NF_HOOK

NF_HOOK

功能:执行hook回调函数
该宏调用NF_HOOK_THRESH来实现具体功能

// Linux 2.6.21中 NF_HOOK是一个宏 
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) \
NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, INT_MIN)

// Linux 3.13.0 中 NF_HOOK是一个函数
static inline int
NF_HOOK(uint8_t pf, unsigned int hook, struct sk_buff *skb,
	struct net_device *in, struct net_device *out,
	int (*okfn)(struct sk_buff *))
{
	return NF_HOOK_THRESH(pf, hook, skb, in, out, okfn, INT_MIN);
}

NF_HOOK_THRESH

功能:执行hook回调函数
相比于NF_HOOK,该宏增加了一个变量thresh,thresh是变量hook回调函数的优先级
A)该宏调用nf_hook_thresh来实现遍历hook回调函数并返回执行操作后的结果
B)若返回值为1,则继续调用回调函数okfn进行后续操作。

// Linux 2.6.21中 NF_HOOK_THRESH是一个宏 
#define NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, thresh)        \
({int __ret;        \
if ((__ret=nf_hook_thresh(pf, hook, &(skb), indev, outdev, okfn, thresh, 1)) == 1)\
__ret = (okfn)(skb);        \
__ret;})

// Linux 3.13.0 中 NF_HOOK_THRESH是一个函数
static inline int
NF_HOOK_THRESH(uint8_t pf, unsigned int hook, struct sk_buff *skb,
	       struct net_device *in, struct net_device *out,
	       int (*okfn)(struct sk_buff *), int thresh)
{
	int ret = nf_hook_thresh(pf, hook, skb, in, out, okfn, thresh);
	if (ret == 1)
		ret = okfn(skb);
	return ret;
}

NF_HOOK_COND

与NF_HOOK_THRESH相比,将thresh值设置了默认值,而增加了cond变量,这两个宏最终都会调用函数nf_hook_thresh

// Linux 2.6.21中 NF_HOOK_COND是一个宏 
#define NF_HOOK_COND(pf, hook, skb, indev, outdev, okfn, cond)        \
({int __ret;        \
if ((__ret=nf_hook_thresh(pf, hook, &(skb), indev, outdev, okfn, INT_MIN, cond)) == 1)\
__ret = (okfn)(skb);        \
__ret;})

// Linux 3.13.0 中 NF_HOOK_COND是一个函数
static inline int
NF_HOOK_COND(uint8_t pf, unsigned int hook, struct sk_buff *skb,
	     struct net_device *in, struct net_device *out,
	     int (*okfn)(struct sk_buff *), bool cond)
{
	int ret;
	if (!cond ||
	    ((ret = nf_hook_thresh(pf, hook, skb, in, out, okfn, INT_MIN)) == 1))
		ret = okfn(skb);
	return ret;
}

nf_hook_slow

这个函数才是真正干活的函数,真正遍历hook链表并执行hook回调函数。
参数:
pf:协议族
hook:hook点
skb:数据包
indev:数据包入口设备
outdev:数据包出口设备
okfn:回调函数(此处不执行)
hook_thresh:起始优先级,只执行该hook点上优先级大于该值所有hook函数

功能:从nf_hooks数组中索引找到协议族为pf,hook点为hook的链表,遍历该链表,找到所有优先级大于或等于hook_thresh的所有nf_hook_ops结构,执行每一个nf_hook_ops结构的hook回调函数。
若调用nf_iterate的返回值是NF_DROP,则释放skb,且返回错误
若返回值为NF_ACCEPT、NF_STOP,则返回1表示允许数据包继续前行
若是NF_QUEUE,则将数据包放入netfilter的队列中,数据包可以从内核传递到用户层处理,并将处理结果返回。

//  Linux 2.6.21
int nf_hook_slow(int pf, unsigned int hook, struct sk_buff **pskb,
                 struct net_device *indev,
                 struct net_device *outdev,
                 int (*okfn)(struct sk_buff *),
                 int hook_thresh)
{
    struct list_head *elem;
    unsigned int verdict;
    int ret = 0;
    /* We may already have this, but read-locks nest anyway */
    rcu_read_lock();
    elem = &nf_hooks[pf][hook];
next_hook:
    verdict = nf_iterate(&nf_hooks[pf][hook], pskb, hook, indev,
                         outdev, &elem, okfn, hook_thresh);
    if (verdict == NF_ACCEPT || verdict == NF_STOP)
    {
        ret = 1;
        goto unlock;
    }
    else if (verdict == NF_DROP)
    {
        kfree_skb(*pskb);
        ret = -EPERM;
    }
    else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE)
    {
        NFDEBUG("nf_hook: Verdict = QUEUE.\n");
        /*nf_queue 是 netfilter 的基本机制--队列模型,可以经内核数据包递交到用户层处理,并根据用户态的处理结果,对数据包进行相应的操作*/
        if (!nf_queue(pskb, elem, pf, hook, indev, outdev, okfn,
                      verdict >> NF_VERDICT_BITS))
            goto next_hook;
    }
unlock:
    rcu_read_unlock();
    return ret;
}
// Linux 3.13.0
/* Returns 1 if okfn() needs to be executed by the caller,
 * -EPERM for NF_DROP, 0 otherwise. */
int nf_hook_slow(u_int8_t pf, unsigned int hook, struct sk_buff *skb,
		 struct net_device *indev,
		 struct net_device *outdev,
		 int (*okfn)(struct sk_buff *),
		 int hook_thresh)
{
	struct nf_hook_ops *elem;
	unsigned int verdict;
	int ret = 0;

	/* We may already have this, but read-locks nest anyway */
	rcu_read_lock();

	elem = list_entry_rcu(&nf_hooks[pf][hook], struct nf_hook_ops, list);
next_hook:
	verdict = nf_iterate(&nf_hooks[pf][hook], skb, hook, indev,
			     outdev, &elem, okfn, hook_thresh);
	if (verdict == NF_ACCEPT || verdict == NF_STOP) {
		ret = 1;
	} else if ((verdict & NF_VERDICT_MASK) == NF_DROP) {
		kfree_skb(skb);
		ret = NF_DROP_GETERR(verdict);
		if (ret == 0)
			ret = -EPERM;
	} else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {
		int err = nf_queue(skb, elem, pf, hook, indev, outdev, okfn,
						verdict >> NF_VERDICT_QBITS);
		if (err < 0) {
			if (err == -ECANCELED)
				goto next_hook;
			if (err == -ESRCH &&
			   (verdict & NF_VERDICT_FLAG_QUEUE_BYPASS))
				goto next_hook;
			kfree_skb(skb);
		}
	}
	rcu_read_unlock();
	return ret;
}

上面就是HOOK机制涉及到的主要的函数,HOOK机制还是比较好理解的,就是这样的机制作为基石,为netfilter的实现提供了坚实的基础,使netfilter能够实现很强大的功能。

实践

icmp_reply_filter.c

主要是数据接收方向的NF_IP_LOCAL_IN点注册回调函数,该回调函数对接收到的icmp reply报文,将序列号是9的倍数的reply报文丢弃掉

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
#include <linux/version.h>
#include <linux/skbuff.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/moduleparam.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/icmp.h>
#include <net/ip.h>
/**
 * 丢失本地接收的seq能被9整除的ICMP响应包
*/
static unsigned int
icmp_reply_hook_func(unsigned int hook,
                     struct sk_buff *skb,
                     const struct net_device *in,
                     const struct net_device *out,
                     int (*okfn)(struct sk_buff *))
{
    const struct iphdr *iph;
    struct icmphdr *icmph;
    if (skb->len < sizeof(struct iphdr) ||
        ip_hdrlen(skb) < sizeof(struct iphdr))
        return NF_ACCEPT;
    iph = ip_hdr(skb);
    icmph = (struct icmphdr *)(iph + 1);
    if (iph->protocol == 1)
    {
        if (icmph->type == 0)
        {
            if ((icmph->un.echo.sequence) % (0x9) == 0)
            {
                printk("----reply packet drop %d---\n", icmph->un.echo.sequence);
                return NF_DROP;
            }
        }
    }
    return NF_ACCEPT;
}

static struct nf_hook_ops __read_mostly icmp_reply_hook =
    {
        .hook = icmp_reply_hook_func,
        .owner = THIS_MODULE,
        .pf = PF_INET,
        .hooknum = NF_INET_LOCAL_IN,
        .priority = NF_IP_PRI_FIRST,
};
// static int __init icmp_reply_hook_init()
static int icmp_reply_hook_init(void)
{
    printk(KERN_INFO "---init---\n");
    return nf_register_hook(&icmp_reply_hook);
}

static void __exit icmp_reply_hook_exit(void)
{
    printk(KERN_INFO "---exit---\n");
    nf_unregister_hook(&icmp_reply_hook);
}

module_init(icmp_reply_hook_init);
module_exit(icmp_reply_hook_exit);
MODULE_AUTHOR("licky");
MODULE_DESCRIPTION("icmp_reply_hook");
MODULE_LICENSE("GPL");

icmp_request_filter.c

主要是数据发送方向的NF_IP_LOCAL_OUT点注册回调函数,该回调函数对发送的icmp request报文,将序列号是5的倍数的request报文丢弃掉。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
#include <linux/version.h>
#include <linux/skbuff.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/moduleparam.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/icmp.h>
#include <net/ip.h>
/**
 *  丢弃本地外出的seq能被5整除的ICMP请求包
 */ 
static unsigned int
icmp_request_hook_func(unsigned int hook,
                       struct sk_buff *skb,
                       const struct net_device *in,
                       const struct net_device *out,
                       int (*okfn)(struct sk_buff *))
{
    const struct iphdr *iph;
    struct icmphdr *icmph;
    if (skb->len < sizeof(struct iphdr) ||
        ip_hdrlen(skb) < sizeof(struct iphdr))
        return NF_ACCEPT;
    iph = ip_hdr(skb);
    icmph = (struct icmphdr *)(iph + 1);
    if (iph->protocol == 1)
    {
        if (icmph->type == 8)
        {
            if ((icmph->un.echo.sequence) % 5 == 0)
            {
                printk("----request packet drop %d---\n", icmph->un.echo.sequence);
                return NF_DROP;
            }
        }
    }
    return NF_ACCEPT;
}

static struct nf_hook_ops __read_mostly icmp_request_hook =
    {
        .hook = icmp_request_hook_func,
        .owner = THIS_MODULE,
        .pf = PF_INET,
        .hooknum = NF_INET_LOCAL_OUT,
        .priority = NF_IP_PRI_FIRST,
};

static int __init icmp_request_init(void)
{
    printk(KERN_INFO "---init---\n");
    return nf_register_hook(&icmp_request_hook);
}

static void __exit icmp_request_exit(void)
{
    printk(KERN_INFO "---exit---\n");
    nf_unregister_hook(&icmp_request_hook);
}
module_init(icmp_request_init);
module_exit(icmp_request_exit);
MODULE_AUTHOR("licky");
MODULE_DESCRIPTION("icm_request_hook");
MODULE_LICENSE("GPL");

MAKEFILE:

KVERSION = $(shell uname -r)

obj-m += icmp_reply_filter.o

obj-m += icmp_request_filter.o

build:kernel_modules

kernel_modules:
	$(MAKE) -C /lib/modules/$(KVERSION)/build SUBDIRS=$(PWD) modules

clean:
	$(MAKE) -C /lib/modules/$(KVERSION)/build M=$(CURDIR) clean
  • 5
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值