【Linux 驱动】Netfilter/iptables (四) 窥探 Netfilter Hook 机制
上篇文章介绍了注册和注销Netfilter/iptables,其中对于hook函数,我们没有具体到数据包的规则处理,直接一律来者皆拒(NF_DROP)。ok,我们接着前面,深入探索下hook函数:typedef unsigned int nf_hookfn(const struct nf_hook_ops *ops,struct sk_buff *s
上篇文章介绍了注册和注销Netfilter/iptables,其中对于hook函数,我们没有具体到数据包的规则处理,直接一律来者皆拒(NF_DROP)。
本系列博文前后衔接比较紧密,重要的数据结构及函数均在前面进行了讲解,这里则窥探整个Netfilter Hook机制。
ok,我们接着前面,深入探索下hook函数:
typedef unsigned int nf_hookfn(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 *));
分析下上面的参数:
- const struct nf_hook_ops *ops
这是3.14版本中的参数,对比之前版本有修改,这里是直接传入 nf_hook_ops 结构体的指针,后面会给出这个结构体的信息。 - struct sk_buff *skb
这个非常非常重要的结构体的介绍见网络协议栈主要数据结构介绍,网络堆栈用 sk_buff 数据结构来描述数据包。 - const struct net_device *in/*out
net_device 数据结构被Linux内核用于描述所有类型的网络接口,在编写网络驱动的时候,这个数据结构非常重要。这两个参数中的in,用于描述数据包到达的接口,参数out用于描述数据包离开的接口。
在通常情况下,这两个参数中只有一个被提供。 - int (okfn)(struct sk_buff )
传递给hook函数的最后一个参数是一个命名为okfn函数指针,该函数以一个sk_buff数据结构作为它唯一的参数,并且返回一个整型的值。在net/core/netfilter.c中查看,有两个地方调用了这个okfn函数。这两个地方是分别在函数nf_hook_slow()中以及函数nf_reinject()中,在协议栈的某个位置,当Netfilter hook的返回值为NF_ACCEPT时被调用。
后面通过内核协议栈中的一段代码我们会看到,这个okfn函数基本上就是内核协议栈中的某个函数。
关于struct nf_hook_ops看这里后面Netfilter hook之struct nf_hook_ops
此外我们要清楚内核是如何从协议栈正常的流程切入到 Netfilter 框架中,然后顺序、依次去调用每个 HOOK 点有所的钩子函数的。
到我们娓娓道来,
先看一组宏定义,下面不是声明函数指针哦(linux/netfilter.h)
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (okfn)(skb)
#define NF_HOOK_COND(pf, hook, skb, indev, outdev, okfn, cond) (okfn)(skb)
再看,
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);
}
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)//上面函数返回1时,将执行okfn函数
ret = okfn(skb);
return ret;
}
/**下面的注释
* nf_hook_thresh - call a netfilter hook
*
* Returns 1 if the hook has allowed the packet to pass. The function
* okfn must be invoked by the caller in this case. Any other return
* value indicates the packet has been consumed by the hook.
*/
/*
返回1,表示hook放行数据包,此时上面的NF_HOOK_THRESH()将执行okfn();如果是返回其余值,表示被hook“调戏”了一番
*/
static inline int nf_hook_thresh(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 thresh)
{
if (nf_hooks_active(pf, hook))//检查是否注册了
return nf_hook_slow(pf, hook, skb, indev, outdev, okfn, thresh);
return 1;//如果没有注册钩子函数,则直接返回1,转而调用okfn函数
}
/* Returns 1 if okfn() needs to be executed by the caller,
* -EPERM for NF_DROP, 0 otherwise. */
//如果需要执行okfn(),则该函数返回1,然后在NF_HOOK_THRESH中就会去调用okfn()
//nf_hook_slow()根据优先级查找双向链表nf_hooks[][],找到对应的回调函数来处理数据包。
//(nf_hook_slow() -> nf_iterate() -> (*elemp)->hook();//我们注册的钩子函数)
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)
好的,上面列了一大串函数,我们来理清一下:
NF_HOOK -> NF_HOOK_THRESH -> nf_hook_thresh(根据这个函数的返回值,决定是否调用okfn函数) -> nf_hook_slow(如果注册了对应的钩子函数,则执行,否则执行okfn对应的函数)
调不调用okfn函数(okfn是个函数指针),其实okfn函数是内核协议栈的一个函数,也就说调用这个函数意味着数据包将继续走协议栈。所以很显然有两种情况下数据包会被okfn函数处理:一种是没有注册钩子函数,即Netfilter没有设关卡;另一种就是注册了钩子函数,Netfilter设有关卡,但是它允许数据包通过继续走协议栈。
具体到代码的实现环节,第一种情况就是nf_hook_thresh函数中的nf_hooks_active函数没有检查到注册有对应的钩子函数,返回1,将在NF_HOOK_THRESH中调用okfn();第二种情况就是检查有钩子函数,那么将执行nf_hook_slow函数,并返回其执行结果给NF_HOOK_THRESH。
nf_hook_slow 将返回以下结果之一,可以看出,只有在钩子函数返回NF_ACCEPT 的情况下,数据包才会执行okfn函数,继续走协议栈。
至于其余情况,我们这里不做讨论。
/* Responses from hook functions. */
#define NF_DROP 0
#define NF_ACCEPT 1
#define NF_STOLEN 2
#define NF_QUEUE 3
#define NF_REPEAT 4
#define NF_STOP 5
#define NF_MAX_VERDICT NF_STOP
到这里,不难理解我们上篇的代码实现结果就是,访问不了网络了,因为我们注册了钩子函数,然后返回NF_DROP,这样数据包就被挡住了。
所以,可以得出,上面的那个宏定义分布在协议栈各个合适位置,作为协议栈嵌入到 Netfilter 框架的切入点。
举个栗子,定位到net/ipv4/ip_input.c 里的ip_rcv 函数(在sourceinsight中,点击然后右键jump to caller,很快就会知道哪些地方调用这个宏。Linux下开发,Windows下看源码)
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);
这个return代码,位于ip_rcv函数的最后。这里面的okfn函数就是ip_rcv_finish(),如果数据包被Netfilter hook函数拦截了(NF_DROP),就不会执行这个函数,数据包也就不会继续走协议栈被我们接收到了。
这段代码的意思就是:
如果协议栈当前收到一个IP报文(NFPROTO_IPV4),那么就把这个报文传到 Netfilter的NF_INET_PRE_ROUTING 过滤点,去检查在那个过滤点 nf_hooks[NFPROTO_IPV4][NF_INET_PRE_ROUTING] 是否已经注册了相关的用于处理数据包的钩子函数。如果有,则挨个遍历链表 nf_hooks[2][0](前面的枚举对应的数字)去寻找匹配的match和target,根据返回到 Netfilter 框架中(NF_HOOK_THRESH)的值来进一步决定如何处理该数据包(由注册的钩子函数处理,还是交由ip_rcv_finish函数处理)。
总的说来,整个Linux内核中Netfilter框架的HOOK机制可以概括如下:
在数据包流经内核协议栈的整个过程中,在一些已预定义的关键点上PRE_ROUTING、LOCAL_IN、FORWARD、LOCAL_OUT和POST_ROUTING会根据数据包的协议簇到这些关键点去查找是否注册有钩子函数。如果没有,则直接返回okfn函数指针所指向的函数继续走协议栈;如果有,则调用nf_hook_slow函数,从而进入到Netfilter框架中去进一步调用已注册在该过滤点下的钩子函数,再根据其返回值来确定是否继续执行由函数指针okfn所指向的函数。
更多推荐
所有评论(0)