作者:gfree.wind@gmail.com
博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
======================================================================================================
相信不少同学都用过iptables,即使没有用过,也基本上都听过。今天开始学习一下iptables在内核部分的实现,即netfilter。
在学习内核代码的过程中,我有一个经验——如果可以把内核对应的应用搞清楚,再去学习内核代码会容易很多。所以,如果没有使用iptables的经验的同学,最好可以先用一用iptables。
我也写过几篇关于iptables的博客,第一篇为http://blog.chinaunix.net/space.php?uid=23629988&do=blog&id=85894
好,开始正题!
我看代码习惯从使用的地方开始。对应netfilter来说,使用它的地方,就是netfilter的挂载点。这方面的介绍可以参见http://blog.chinaunix.net/space.php?uid=23629988&do=blog&id=85894
NF_HOOK为netfilter的挂载点的调用函数,在kernel中搜索该关键字,搜索结果如下:
从函数的名字以及第三列的代码,可以看出netfilter确实只有五个挂载点,prerouting,local in,forward,local out 和postrouting。不过针对不同的使用,似乎有不同的五个挂载点的定义。如对应brige来说,有NF_BR_PRE_ROUTING,NF_BR_PRE_ROUTING等,而ARP也有自己的NF_ARP_IN,NF_ARP_IN等定义。
为了搞清楚这个,让我们去看NF_HOOK的代码。NF_HOOK->NF_HOOK_THRESH->nf_hook_thresh->nf_hook_slow——这个是最终的执行函数。
- /* 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 list_head *elem;
-
unsigned int verdict;
-
int ret = 0;
-
-
/* We may already have this, but read-locks nest anyway */
-
rcu_read_lock();
/*
根据协议pf和挂载点类型,取得第一个元素。
nf_hooks为一个二维数组,其类型为一个双链表list_head。它最大行数为NFPROTO_NUMPROTO,即支持的协议 最大个数,分别为NFPROTO_UNSPEC,NFPROTO_IPV4,NFPROTO_ARP,NFPROTO_BRIDGE,NFPROTO_IPV6,N
FPROTO_DECNET。而最大列数为NF_MAX_HOOKS,即为最大挂载点个数+1。
看到这里,我来猜测一下为什么netfilter要区分协议:
1. 通过区分协议,简化了数据包的parse过程;
2. 指定协议,也方便用户配置。
*/
-
elem = &nf_hooks[pf][hook];
-
next_hook:
- /* 开始遍历对应的netfilter的规则,即对应的proto和hook挂载点 */
-
verdict = nf_iterate(&nf_hooks[pf][hook], skb, hook, indev,
-
outdev, &elem, okfn, hook_thresh);
-
if (verdict == NF_ACCEPT || verdict == NF_STOP) {
- /* 判定结果为NF_ACCEPT和NF_STOP,返回结果。即pass所有规则 */
-
ret = 1;
-
} else if (verdict == NF_DROP) {
- /* 要drop掉这个数据包 */
-
kfree_skb(skb);
-
ret = -EPERM;
-
} else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {
- /*
- 判定结果有enque,即将数据包传给用户空间的queue handler。
- 这个的verdict被重用了。低16位被用于存储判定结果,而高16位用于存储enque的数量。
- */
-
if (!nf_queue(skb, elem, pf, hook, indev, outdev, okfn,
-
verdict >> NF_VERDICT_BITS))
-
goto next_hook; //只有在这个elem无效时,nf_queue才会返回0,继续下面的hook判定。
-
}
-
rcu_read_unlock();
-
return ret;
- }
下面进入nf_iterate
- unsigned int nf_iterate(struct list_head *head,
-
struct sk_buff *skb,
-
unsigned int hook,
-
const struct net_device *indev,
-
const struct net_device *outdev,
-
struct list_head **i,
-
int (*okfn)(struct sk_buff *),
-
int hook_thresh)
-
{
-
unsigned int verdict;
-
-
/*
-
* The caller must not block between calls to this
-
* function because of risk of continuing from deleted element.
-
*/
-
list_for_each_continue_rcu(*i, head) {
-
struct nf_hook_ops *elem = (struct nf_hook_ops *)*i;
/*
规则的优先级判断,目前从NF_HOOK到这里的优先级为INT_MIN,即最小。这种情况下,所以的规则都会被检 查。
这个hook_thresh用于保证某些规则在某些挂载点不起作用。
搜索NF_HOOK_THRESH关键字,可以发现对于协议NFPROTO_BRIDGE的挂载点NF_BR_PRE_ROUTING,其thr eash被设为1,这样保证仅部分规则起作用。
*/
-
if (hook_thresh > elem->priority)
-
continue;
-
-
/* Optimization: we don't need to hold module
-
reference here, since function can't sleep. --RR */
-
verdict = elem->hook(hook, skb, indev, outdev, okfn);
-
if (verdict != NF_ACCEPT) {
- /* 不等于ACCEPT,就可能直接返回判定结果 */
-
#ifdef CONFIG_NETFILTER_DEBUG
-
if (unlikely((verdict & NF_VERDICT_MASK)
-
> NF_MAX_VERDICT)) {
-
NFDEBUG("Evil return from %p(%u).\n",
-
elem->hook, hook);
-
continue;
-
}
-
#endif
- /* 还需要不能等于NF_REPEAT。也就是说既不能等于NF_ACCEPT和NF_REPEAT,即可直接返回判定结
- 果,无需后面的判定 */
-
if (verdict != NF_REPEAT)
-
return verdict;
/* 判定结果为NF_REPEAT,则重复这个规则的判定 */
-
*i = (*i)->prev;
-
}
-
}
/* 所有判定结果都为NF_ACCEPT,才可返回NF_ACCEPT */
-
return NF_ACCEPT;
- }
从今天的这两个函数的学习,可以理解netfilter的判定结果的含义了。
NF_DROP:直接drop掉这个数据包;
NF_ACCEPT:数据包通过了挂载点的所有规则;
NF_STOLEN:这个还未出现,留在以后解释;
NF_QUEUE:将数据包enque到用户空间的enque handler;
NF_REPEAT:为netfilter的一个内部判定结果,需要重复该条规则的判定,直至不为NF_REPEAT;
NF_STOP:数据包通过了挂载点的所有规则。但与NF_ACCEPT不同的一点时,当某条规则的判定结果为NF_STOP,那么可以直接返回结果NF_STOP,无需进行后面的判定了。而NF_ACCEPT需要所以的规则都为ACCEPT,才能返回NF_ACCEPT。